From 48b839ce2a1cb39097d54df03b6be9185df1bb77 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Tue, 10 Sep 2024 22:33:50 +0900 Subject: [PATCH] Added handling of ERC-20 deposits (#32) --- packages/localnet/src/createToken.ts | 142 ++++++ packages/localnet/src/deployOpts.ts | 4 + packages/localnet/src/handleOnEVMCalled.ts | 63 +++ packages/localnet/src/handleOnEVMDeposited.ts | 94 ++++ packages/localnet/src/handleOnRevertEVM.ts | 54 +++ packages/localnet/src/handleOnRevertZEVM.ts | 34 ++ packages/localnet/src/handleOnZEVMCalled.ts | 49 ++ .../localnet/src/handleOnZEVMWithdrawn.ts | 89 ++++ packages/localnet/src/index.ts | 450 +++--------------- packages/localnet/src/log.ts | 12 + packages/tasks/src/localnet.ts | 21 +- 11 files changed, 617 insertions(+), 395 deletions(-) create mode 100644 packages/localnet/src/createToken.ts create mode 100644 packages/localnet/src/deployOpts.ts create mode 100644 packages/localnet/src/handleOnEVMCalled.ts create mode 100644 packages/localnet/src/handleOnEVMDeposited.ts create mode 100644 packages/localnet/src/handleOnRevertEVM.ts create mode 100644 packages/localnet/src/handleOnRevertZEVM.ts create mode 100644 packages/localnet/src/handleOnZEVMCalled.ts create mode 100644 packages/localnet/src/handleOnZEVMWithdrawn.ts create mode 100644 packages/localnet/src/log.ts diff --git a/packages/localnet/src/createToken.ts b/packages/localnet/src/createToken.ts new file mode 100644 index 0000000..4417b84 --- /dev/null +++ b/packages/localnet/src/createToken.ts @@ -0,0 +1,142 @@ +import { ethers } from "ethers"; +import * as ZRC20 from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; +import { deployOpts } from "./deployOpts"; +import * as TestERC20 from "@zetachain/protocol-contracts/abi/TestERC20.sol/TestERC20.json"; + +export const createToken = async ({ + fungibleModuleSigner, + deployer, + systemContract, + gatewayZEVM, + foreignCoins, + custody, + tss, + uniswapFactoryInstance, + wzeta, + uniswapRouterInstance, + symbol, + isGasToken = false, +}: { + fungibleModuleSigner: any; + deployer: ethers.Signer; + systemContract: any; + gatewayZEVM: any; + foreignCoins: any[]; + custody: ethers.BaseContract; + tss: ethers.Signer; + uniswapFactoryInstance: ethers.BaseContract; + wzeta: ethers.BaseContract; + uniswapRouterInstance: ethers.BaseContract; + symbol: string; + isGasToken: boolean; +}) => { + let erc20; + + const zrc20Factory = new ethers.ContractFactory( + ZRC20.abi, + ZRC20.bytecode, + deployer + ); + const zrc20 = await zrc20Factory + .connect(fungibleModuleSigner) + .deploy( + `ZRC-20 ${symbol}`, + `ZRC20${symbol}`, + 18, + 1, + 1, + 1, + systemContract.target, + gatewayZEVM.target, + deployOpts + ); + + if (!isGasToken) { + const erc20Factory = new ethers.ContractFactory( + TestERC20.abi, + TestERC20.bytecode, + deployer + ); + erc20 = await erc20Factory.deploy(symbol, symbol, deployOpts); + const erc20Decimals = await (erc20 as any).connect(deployer).decimals(); + + await (erc20 as any) + .connect(deployer) + .approve(custody.target, ethers.MaxUint256, deployOpts); + + await (erc20 as any) + .connect(deployer) + .mint( + custody.target, + ethers.parseUnits("1000000", erc20Decimals), + deployOpts + ); + await (erc20 as any) + .connect(deployer) + .mint( + await deployer.getAddress(), + ethers.parseUnits("1000000", erc20Decimals), + deployOpts + ); + await (custody as any).connect(tss).whitelist(erc20.target, deployOpts); + + (systemContract as any) + .connect(fungibleModuleSigner) + .setGasCoinZRC20(1, zrc20.target); + (systemContract as any).connect(fungibleModuleSigner).setGasPrice(1, 1); + } + + foreignCoins.push({ + zrc20_contract_address: zrc20.target, + asset: isGasToken ? "" : (erc20 as any).target, + foreign_chain_id: "1", + decimals: 18, + name: `ZetaChain ZRC-20 ${symbol}`, + symbol: `${symbol}.ETH`, + coin_type: isGasToken ? "Gas" : "ERC20", + gas_limit: null, + paused: null, + liquidity_cap: null, + }); + + (zrc20 as any).deposit( + await deployer.getAddress(), + ethers.parseEther("1000"), + deployOpts + ); + + await (wzeta as any) + .connect(deployer) + .deposit({ value: ethers.parseEther("1000"), ...deployOpts }); + + await (uniswapFactoryInstance as any).createPair( + zrc20.target, + wzeta.target, + deployOpts + ); + await (zrc20 as any) + .connect(deployer) + .approve( + uniswapRouterInstance.getAddress(), + ethers.parseEther("1000"), + deployOpts + ); + await (wzeta as any) + .connect(deployer) + .approve( + uniswapRouterInstance.getAddress(), + ethers.parseEther("1000"), + deployOpts + ); + await (uniswapRouterInstance as any).addLiquidity( + zrc20.target, + wzeta.target, + ethers.parseUnits("100", await (zrc20 as any).decimals()), // Amount of ZRC-20 + ethers.parseUnits("100", await (wzeta as any).decimals()), // Amount of ZETA + ethers.parseUnits("90", await (zrc20 as any).decimals()), // Min amount of ZRC-20 to add (slippage tolerance) + ethers.parseUnits("90", await (wzeta as any).decimals()), // Min amount of ZETA to add (slippage tolerance) + await deployer.getAddress(), + Math.floor(Date.now() / 1000) + 60 * 10, // Deadline + deployOpts + ); +}; diff --git a/packages/localnet/src/deployOpts.ts b/packages/localnet/src/deployOpts.ts new file mode 100644 index 0000000..7e62868 --- /dev/null +++ b/packages/localnet/src/deployOpts.ts @@ -0,0 +1,4 @@ +export const deployOpts = { + gasPrice: 10000000000, + gasLimit: 6721975, +}; diff --git a/packages/localnet/src/handleOnEVMCalled.ts b/packages/localnet/src/handleOnEVMCalled.ts new file mode 100644 index 0000000..0051946 --- /dev/null +++ b/packages/localnet/src/handleOnEVMCalled.ts @@ -0,0 +1,63 @@ +import { ethers, NonceManager } from "ethers"; +import { handleOnRevertEVM } from "./handleOnRevertEVM"; +import { log, logErr } from "./log"; +import { deployOpts } from "./deployOpts"; + +// event Called(address indexed sender, address indexed receiver, bytes payload, RevertOptions revertOptions); +export const handleOnEVMCalled = async ({ + tss, + provider, + protocolContracts, + args, + deployer, + fungibleModuleSigner, +}: { + tss: any; + provider: ethers.JsonRpcProvider; + protocolContracts: any; + args: any; + deployer: any; + fungibleModuleSigner: any; +}) => { + log("EVM", "Gateway: 'Called' event emitted"); + try { + const receiver = args[1]; + const message = args[2]; + + (deployer as NonceManager).reset(); + const context = { + origin: protocolContracts.gatewayZEVM.target, + sender: await fungibleModuleSigner.getAddress(), + chainID: 1, + }; + const zrc20 = protocolContracts.zrc20Eth.target; + log( + "ZetaChain", + `Universal contract ${receiver} executing onCrossChainCall (context: ${JSON.stringify( + context + )}), zrc20: ${zrc20}, amount: 0, message: ${message})` + ); + const executeTx = await protocolContracts.gatewayZEVM + .connect(fungibleModuleSigner) + .execute(context, zrc20, 0, receiver, message, deployOpts); + await executeTx.wait(); + const logs = await provider.getLogs({ + address: receiver, + fromBlock: "latest", + }); + + logs.forEach((data) => { + log("ZetaChain", `Event from onCrossChainCall: ${JSON.stringify(data)}`); + }); + } catch (e: any) { + logErr("ZetaChain", `Error executing onCrossChainCall: ${e}`); + const revertOptions = args[3]; + await handleOnRevertEVM({ + revertOptions, + err: e, + tss, + provider, + protocolContracts, + }); + } +}; diff --git a/packages/localnet/src/handleOnEVMDeposited.ts b/packages/localnet/src/handleOnEVMDeposited.ts new file mode 100644 index 0000000..25c7c6a --- /dev/null +++ b/packages/localnet/src/handleOnEVMDeposited.ts @@ -0,0 +1,94 @@ +import { ethers, NonceManager } from "ethers"; +import { handleOnRevertEVM } from "./handleOnRevertEVM"; +import { log, logErr } from "./log"; +import { deployOpts } from "./deployOpts"; + +// event Deposited(address indexed sender, address indexed receiver, uint256 amount, address asset, bytes payload, RevertOptions revertOptions); +export const handleOnEVMDeposited = async ({ + tss, + provider, + protocolContracts, + args, + deployer, + fungibleModuleSigner, + foreignCoins, +}: { + tss: any; + provider: ethers.JsonRpcProvider; + protocolContracts: any; + args: any; + deployer: any; + fungibleModuleSigner: any; + foreignCoins: any[]; +}) => { + 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 zrc20 = foreignCoin.zrc20_contract_address; + + const context = { + origin: protocolContracts.gatewayZEVM.target, + sender: await fungibleModuleSigner.getAddress(), + chainID: 1, + }; + + // If message is not empty, execute depositAndCall + if (message !== "0x") { + log( + "ZetaChain", + `Universal contract ${receiver} executing onCrossChainCall (context: ${JSON.stringify( + context + )}), zrc20: ${zrc20}, amount: ${amount}, message: ${message})` + ); + + const tx = await protocolContracts.gatewayZEVM + .connect(fungibleModuleSigner) + .depositAndCall(context, zrc20, amount, receiver, message, deployOpts); + + await tx.wait(); + const logs = await provider.getLogs({ + address: receiver, + fromBlock: "latest", + }); + + logs.forEach((data) => { + log( + "ZetaChain", + `Event from onCrossChainCall: ${JSON.stringify(data)}` + ); + }); + } else { + const tx = await protocolContracts.gatewayZEVM + .connect(fungibleModuleSigner) + .deposit(zrc20, amount, receiver, deployOpts); + await tx.wait(); + log("ZetaChain", `Deposited ${amount} of ${zrc20} tokens to ${receiver}`); + } + } catch (e: any) { + logErr("ZetaChain", `Error depositing: ${e}`); + const revertOptions = args[5]; + await handleOnRevertEVM({ + revertOptions, + err: e, + tss, + provider, + protocolContracts, + }); + } +}; diff --git a/packages/localnet/src/handleOnRevertEVM.ts b/packages/localnet/src/handleOnRevertEVM.ts new file mode 100644 index 0000000..e0cd943 --- /dev/null +++ b/packages/localnet/src/handleOnRevertEVM.ts @@ -0,0 +1,54 @@ +import { log, logErr } from "./log"; +import { deployOpts } from "./deployOpts"; +import { ethers, NonceManager } from "ethers"; + +export const handleOnRevertEVM = async ({ + revertOptions, + err, + provider, + tss, + protocolContracts, +}: { + revertOptions: any; + err: any; + provider: any; + tss: any; + protocolContracts: any; +}) => { + const callOnRevert = revertOptions[1]; + const revertAddress = revertOptions[0]; + const revertMessage = revertOptions[3]; + const revertContext = { + asset: ethers.ZeroAddress, + amount: 0, + revertMessage, + }; + if (callOnRevert) { + try { + log( + "EVM", + `Contract ${revertAddress} executing onRevert (context: ${JSON.stringify( + revertContext + )})` + ); + (tss as NonceManager).reset(); + const tx = await protocolContracts.gatewayEVM + .connect(tss) + .executeRevert(revertAddress, "0x", revertContext, deployOpts); + await tx.wait(); + log("EVM", "Gateway: successfully called onRevert"); + const logs = await provider.getLogs({ + address: revertAddress, + fromBlock: "latest", + }); + + logs.forEach((data: any) => { + log("EVM", `Event from onRevert: ${JSON.stringify(data)}`); + }); + } catch (e: any) { + logErr("EVM", `Gateway: Call onRevert failed: ${e}`); + } + } else { + logErr("EVM", `Tx reverted without callOnRevert: ${err}`); + } +}; diff --git a/packages/localnet/src/handleOnRevertZEVM.ts b/packages/localnet/src/handleOnRevertZEVM.ts new file mode 100644 index 0000000..81fa25f --- /dev/null +++ b/packages/localnet/src/handleOnRevertZEVM.ts @@ -0,0 +1,34 @@ +import { ethers, NonceManager } from "ethers"; + +export const handleOnRevertZEVM = async ( + revertOptions: any, + err: any, + tss: NonceManager, + log: (chain: "EVM" | "ZetaChain", ...messages: string[]) => void, + protocolContracts: any, + deployOpts: any +) => { + const callOnRevert = revertOptions[1]; + const revertAddress = revertOptions[0]; + const revertMessage = revertOptions[3]; + const revertContext = { + asset: ethers.ZeroAddress, + amount: 0, + revertMessage, + }; + + if (callOnRevert) { + log("ZetaChain", "Gateway: calling executeRevert"); + try { + tss.reset(); + await protocolContracts.gatewayZEVM + .connect(tss) + .executeRevert(revertAddress, revertContext, deployOpts); + log("ZetaChain", "Gateway: Call onRevert success"); + } catch (e) { + log("ZetaChain", `Gateway: Call onRevert failed: ${e}`); + } + } else { + log("ZetaChain", "Tx reverted without callOnRevert: ", err); + } +}; diff --git a/packages/localnet/src/handleOnZEVMCalled.ts b/packages/localnet/src/handleOnZEVMCalled.ts new file mode 100644 index 0000000..82f3728 --- /dev/null +++ b/packages/localnet/src/handleOnZEVMCalled.ts @@ -0,0 +1,49 @@ +import { ethers, NonceManager } from "ethers"; +import { handleOnRevertZEVM } from "./handleOnRevertZEVM"; +import { log, logErr } from "./log"; +import { deployOpts } from "./deployOpts"; + +// event Called(address indexed sender, address indexed zrc20, bytes receiver, bytes message, uint256 gasLimit, RevertOptions revertOptions); +export const handleOnZEVMCalled = async ({ + tss, + provider, + protocolContracts, + args, +}: { + tss: any; + provider: ethers.JsonRpcProvider; + protocolContracts: any; + args: any; +}) => { + log("ZetaChain", "Gateway: 'Called' event emitted"); + try { + tss.reset(); + const receiver = args[2]; + const message = args[3]; + log("EVM", `Calling ${receiver} with message ${message}`); + + const executeTx = await protocolContracts.gatewayEVM + .connect(tss) + .execute(receiver, message, deployOpts); + + const logs = await provider.getLogs({ + address: receiver, + fromBlock: "latest", + }); + + logs.forEach((data) => { + log("EVM", `Event from contract: ${JSON.stringify(data)}`); + }); + await executeTx.wait(); + } catch (e) { + const revertOptions = args[5]; + await handleOnRevertZEVM( + revertOptions, + e, + tss, + log, + protocolContracts, + deployOpts + ); + } +}; diff --git a/packages/localnet/src/handleOnZEVMWithdrawn.ts b/packages/localnet/src/handleOnZEVMWithdrawn.ts new file mode 100644 index 0000000..f1a2ff4 --- /dev/null +++ b/packages/localnet/src/handleOnZEVMWithdrawn.ts @@ -0,0 +1,89 @@ +import { ethers, NonceManager } from "ethers"; +import { handleOnRevertZEVM } from "./handleOnRevertZEVM"; +import { log, logErr } from "./log"; +import { deployOpts } from "./deployOpts"; +import * as ZRC20 from "@zetachain/protocol-contracts/abi/ZRC20.sol/ZRC20.json"; + +// event Withdrawn(address indexed sender, uint256 indexed chainId, bytes receiver, address zrc20, uint256 value, uint256 gasfee, uint256 protocolFlatFee, bytes message, uint256 gasLimit, RevertOptions revertOptions); +export const handleOnZEVMWithdrawn = async ({ + tss, + provider, + protocolContracts, + args, + deployer, + foreignCoins, +}: { + tss: any; + provider: ethers.JsonRpcProvider; + protocolContracts: any; + args: any; + deployer: any; + foreignCoins: any[]; +}) => { + log("ZetaChain", "Gateway: 'Withdrawn' event emitted"); + try { + const receiver = args[2]; + const zrc20 = args[3]; + const amount = args[4]; + const message = args[7]; + (tss as NonceManager).reset(); + if (message !== "0x") { + log("EVM", `Calling ${receiver} with message ${message}`); + const executeTx = await protocolContracts.gatewayEVM + .connect(tss) + .execute(receiver, message, deployOpts); + await executeTx.wait(); + const logs = await provider.getLogs({ + address: receiver, + fromBlock: "latest", + }); + logs.forEach((data) => { + log("EVM", `Event from contract: ${JSON.stringify(data)}`); + }); + } else { + const zrc20Contract = new ethers.Contract(zrc20, ZRC20.abi, deployer); + const coinType = await zrc20Contract.COIN_TYPE(); + if (coinType === 1n) { + const tx = await tss.sendTransaction({ + to: receiver, + value: amount, + ...deployOpts, + }); + await tx.wait(); + log( + "EVM", + `Transferred ${ethers.formatEther( + amount + )} native gas tokens from TSS to ${receiver}` + ); + } else if (coinType === 2n) { + const foreignCoin = foreignCoins.find( + (coin: any) => coin.zrc20_contract_address === zrc20 + ); + if (!foreignCoin) { + logErr("EVM", `Foreign coin not found for ZRC20 address: ${zrc20}`); + return; + } + const erc20 = foreignCoin.asset; + const tx = await protocolContracts.custody + .connect(tss) + .withdraw(receiver, erc20, amount, deployOpts); + await tx.wait(); + log( + "EVM", + `Transferred ${amount} ERC-20 tokens from Custody to ${receiver}` + ); + } + } + } catch (e) { + const revertOptions = args[9]; + await handleOnRevertZEVM( + revertOptions, + e, + tss, + log, + protocolContracts, + deployOpts + ); + } +}; diff --git a/packages/localnet/src/index.ts b/packages/localnet/src/index.ts index cc1400e..b3d6c68 100755 --- a/packages/localnet/src/index.ts +++ b/packages/localnet/src/index.ts @@ -10,30 +10,20 @@ import * as ZetaConnectorNonNative from "@zetachain/protocol-contracts/abi/ZetaC import * as WETH9 from "@zetachain/protocol-contracts/abi/WZETA.sol/WETH9.json"; import * as UniswapV2Factory from "@uniswap/v2-core/build/UniswapV2Factory.json"; import * as UniswapV2Router02 from "@uniswap/v2-periphery/build/UniswapV2Router02.json"; -import ansis from "ansis"; +import { handleOnZEVMCalled } from "./handleOnZEVMCalled"; +import { handleOnEVMCalled } from "./handleOnEVMCalled"; +import { deployOpts } from "./deployOpts"; +import { handleOnEVMDeposited } from "./handleOnEVMDeposited"; +import { handleOnZEVMWithdrawn } from "./handleOnZEVMWithdrawn"; +import { createToken } from "./createToken"; const FUNGIBLE_MODULE_ADDRESS = "0x735b14BB79463307AAcBED86DAf3322B1e6226aB"; -const zrc20Assets: any = {}; - -const log = (chain: "EVM" | "ZetaChain", ...messages: string[]) => { - const color = chain === "ZetaChain" ? ansis.green : ansis.cyan; - const combinedMessage = messages.join(" "); - console.log(color(`[${ansis.bold(chain)}]: ${combinedMessage}`)); -}; - -const logErr = (chain: "EVM" | "ZetaChain", ...messages: string[]) => { - const combinedMessage = messages.join(" "); - log(chain, ansis.red(combinedMessage)); -}; +const foreignCoins: any[] = []; let protocolContracts: any; let deployer: Signer; let tss: Signer; -const deployOpts = { - gasPrice: 10000000000, - gasLimit: 6721975, -}; const prepareUniswap = async (deployer: Signer, TSS: Signer, wzeta: any) => { const uniswapFactory = new ethers.ContractFactory( @@ -214,143 +204,35 @@ const deployProtocolContracts = async ( await uniswapRouterInstance.getAddress() ); - const zrc20Factory = new ethers.ContractFactory( - ZRC20.abi, - ZRC20.bytecode, - deployer - ); - - const zrc20Eth = await zrc20Factory - .connect(fungibleModuleSigner) - .deploy( - "ZRC-20 ETH", - "ZRC20ETH", - 18, - 1, - 1, - 1, - systemContract.target, - gatewayZEVM.target, - deployOpts - ); - - const zrc20Usdc = await zrc20Factory - .connect(fungibleModuleSigner) - .deploy( - "ZRC-20 USDC", - "ZRC20USDC", - 6, - 1, - 2, - 1, - systemContract.target, - gatewayZEVM.target, - deployOpts - ); - - const testERC20Factory = new ethers.ContractFactory( - TestERC20.abi, - TestERC20.bytecode, - deployer - ); - const testERC20USDC = await testERC20Factory.deploy( - "usdc", - "USDC", - deployOpts - ); - - await (testERC20USDC as any) - .connect(deployer) - .approve(custody.target, ethers.MaxUint256, deployOpts); - - await (testERC20USDC as any) - .connect(deployer) - .mint(custody.target, ethers.parseUnits("1000000", 6), deployOpts); - - zrc20Assets[zrc20Usdc.target as any] = testERC20USDC.target; - await (custody as any) - .connect(tss) - .whitelist(testERC20USDC.target, deployOpts); - - (zrc20Eth as any).deposit( - await deployer.getAddress(), - ethers.parseEther("1000"), - deployOpts - ); - (zrc20Usdc as any).deposit( - await deployer.getAddress(), - ethers.parseEther("1000"), - deployOpts - ); - await (wzeta as any) - .connect(deployer) - .deposit({ value: ethers.parseEther("1000"), ...deployOpts }); - - await (uniswapFactoryInstance as any).createPair( - zrc20Eth.target, - wzeta.target, - deployOpts - ); - - await (uniswapFactoryInstance as any).createPair( - zrc20Usdc.target, - wzeta.target, - deployOpts - ); - - // Approve Router to spend tokens - await (zrc20Eth as any) - .connect(deployer) - .approve( - uniswapRouterInstance.getAddress(), - ethers.parseEther("1000"), - deployOpts - ); - await (wzeta as any) - .connect(deployer) - .approve( - uniswapRouterInstance.getAddress(), - ethers.parseEther("1000"), - deployOpts - ); - await (zrc20Usdc as any) - .connect(deployer) - .approve( - uniswapRouterInstance.getAddress(), - ethers.parseEther("1000"), - deployOpts - ); - - // Add Liquidity to ETH/ZETA pool - await (uniswapRouterInstance as any).addLiquidity( - zrc20Eth.target, - wzeta.target, - ethers.parseUnits("100", await (zrc20Eth as any).decimals()), // Amount of ZRC-20 ETH - ethers.parseUnits("100", await (wzeta as any).decimals()), // Amount of ZETA - ethers.parseUnits("90", await (zrc20Eth as any).decimals()), // Min amount of ZRC-20 ETH to add (slippage tolerance) - ethers.parseUnits("90", await (wzeta as any).decimals()), // Min amount of ZETA to add (slippage tolerance) - await deployer.getAddress(), - Math.floor(Date.now() / 1000) + 60 * 10, // Deadline - deployOpts - ); - - // Add Liquidity to USDC/ZETA pool - await (uniswapRouterInstance as any).addLiquidity( - zrc20Usdc.target, - wzeta.target, - ethers.parseUnits("100", await (zrc20Usdc as any).decimals()), // Amount of ZRC-20 USDC - ethers.parseUnits("100", await (wzeta as any).decimals()), // Amount of ZETA - ethers.parseUnits("90", await (zrc20Usdc as any).decimals()), // Min amount of ZRC-20 USDC to add (slippage tolerance) - ethers.parseUnits("90", await (wzeta as any).decimals()), // Min amount of ZETA to add (slippage tolerance) - await deployer.getAddress(), - Math.floor(Date.now() / 1000) + 60 * 10, // Deadline - deployOpts - ); + await createToken({ + fungibleModuleSigner, + deployer, + systemContract, + gatewayZEVM, + foreignCoins, + custody, + tss, + uniswapFactoryInstance, + wzeta, + uniswapRouterInstance, + symbol: "USDC", + isGasToken: false, + }); - (systemContract as any) - .connect(fungibleModuleSigner) - .setGasCoinZRC20(1, zrc20Eth.target); - (systemContract as any).connect(fungibleModuleSigner).setGasPrice(1, 1); + await createToken({ + fungibleModuleSigner, + deployer, + systemContract, + gatewayZEVM, + foreignCoins, + custody, + tss, + uniswapFactoryInstance, + wzeta, + uniswapRouterInstance, + symbol: "ETH", + isGasToken: true, + }); await (wzeta as any) .connect(fungibleModuleSigner) @@ -374,9 +256,6 @@ const deployProtocolContracts = async ( testEVMZeta, wzeta, tss, - zrc20Eth, - zrc20Usdc, - testERC20USDC, uniswapFactoryInstance, uniswapRouterInstance, uniswapFactoryAddressZetaChain: await uniswapFactoryInstance.getAddress(), @@ -419,247 +298,44 @@ export const initLocalnet = async (port: number) => { ); // Listen to contracts events - // event Called(address indexed sender, address indexed zrc20, bytes receiver, bytes message, uint256 gasLimit, RevertOptions revertOptions); protocolContracts.gatewayZEVM.on("Called", async (...args: Array) => { - log("ZetaChain", "Gateway: 'Called' event emitted"); - try { - (tss as NonceManager).reset(); - - const receiver = args[2]; - const message = args[3]; - log("EVM", `Calling ${receiver} with message ${message}`); - const executeTx = await protocolContracts.gatewayEVM - .connect(tss) - .execute(receiver, message, deployOpts); - - const logs = await provider.getLogs({ - address: receiver, - fromBlock: "latest", - }); - - logs.forEach((data) => { - log("EVM", `Event from contract: ${JSON.stringify(data)}`); - }); - await executeTx.wait(); - } catch (e) { - const revertOptions = args[5]; - await handleOnRevertZEVM(revertOptions, e); - } + handleOnZEVMCalled({ tss, provider, protocolContracts, args }); }); - // event Withdrawn(address indexed sender, uint256 indexed chainId, bytes receiver, address zrc20, uint256 value, uint256 gasfee, uint256 protocolFlatFee, bytes message, uint256 gasLimit, RevertOptions revertOptions); protocolContracts.gatewayZEVM.on("Withdrawn", async (...args: Array) => { - try { - const receiver = args[2]; - const zrc20 = args[3]; - const amount = args[4]; - const message = args[7]; - (tss as NonceManager).reset(); - - if (message !== "0x") { - const executeTx = await protocolContracts.gatewayEVM - .connect(tss) - .execute(receiver, message, deployOpts); - await executeTx.wait(); - } else { - const zrc20Contract = new ethers.Contract(zrc20, ZRC20.abi, deployer); - const coinType = await zrc20Contract.COIN_TYPE(); - if (coinType === 1n) { - const tx = await tss.sendTransaction({ - to: receiver, - value: amount, - ...deployOpts, - }); - await tx.wait(); - log( - "EVM", - `Transferred ${ethers.formatEther( - amount - )} native gas tokens from TSS to ${receiver}` - ); - } else if (coinType === 2n) { - const erc20 = zrc20Assets[zrc20]; - const tx = await protocolContracts.custody - .connect(tss) - .withdraw(receiver, erc20, amount, deployOpts); - await tx.wait(); - log( - "EVM", - `Transferred ${amount} ERC-20 tokens from Custody to ${receiver}` - ); - } - } - } catch (e) { - const revertOptions = args[9]; - await handleOnRevertZEVM(revertOptions, e); - } + handleOnZEVMWithdrawn({ + tss, + provider, + protocolContracts, + args, + deployer, + foreignCoins, + }); }); - // event Called(address indexed sender, address indexed receiver, bytes payload, RevertOptions revertOptions); protocolContracts.gatewayEVM.on("Called", async (...args: Array) => { - log("EVM", "Gateway: 'Called' event emitted"); - try { - const receiver = args[1]; - const message = args[2]; - - (deployer as NonceManager).reset(); - const context = { - origin: protocolContracts.gatewayZEVM.target, - sender: await fungibleModuleSigner.getAddress(), - chainID: 1, - }; - const zrc20 = protocolContracts.zrc20Eth.target; - log( - "ZetaChain", - `Universal contract ${receiver} executing onCrossChainCall (context: ${JSON.stringify( - context - )}), zrc20: ${zrc20}, amount: 0, message: ${message})` - ); - const executeTx = await protocolContracts.gatewayZEVM - .connect(fungibleModuleSigner) - .execute(context, zrc20, 0, receiver, message, deployOpts); - await executeTx.wait(); - const logs = await provider.getLogs({ - address: receiver, - fromBlock: "latest", - }); - - logs.forEach((data) => { - log( - "ZetaChain", - `Event from onCrossChainCall: ${JSON.stringify(data)}` - ); - }); - } catch (e: any) { - logErr("ZetaChain", `Error executing onCrossChainCall: ${e}`); - const revertOptions = args[3]; - await handleOnRevertEVM(revertOptions, e); - } + handleOnEVMCalled({ + tss, + provider, + protocolContracts, + args, + deployer, + fungibleModuleSigner, + }); }); - // event Deposited(address indexed sender, address indexed receiver, uint256 amount, address asset, bytes payload, RevertOptions revertOptions); protocolContracts.gatewayEVM.on("Deposited", async (...args: Array) => { - log("EVM", "Gateway: 'Deposited' event emitted"); - try { - const receiver = args[1]; - const amount = args[2]; - const message = args[4]; - const context = { - origin: protocolContracts.gatewayZEVM.target, - sender: await fungibleModuleSigner.getAddress(), - chainID: 1, - }; - const zrc20 = protocolContracts.zrc20Eth.target; - // If message is not empty, execute depositAndCall - if (message !== "0x") { - log( - "ZetaChain", - `Universal contract ${receiver} executing onCrossChainCall (context: ${JSON.stringify( - context - )}), zrc20: ${zrc20}, amount: ${amount}, message: ${message})` - ); - const depositAndCallTx = await (protocolContracts.gatewayZEVM as any) - .connect(fungibleModuleSigner) - .depositAndCall( - context, - zrc20, - amount, - receiver, - message, - deployOpts - ); - - await depositAndCallTx.wait(); - const logs = await provider.getLogs({ - address: receiver, - fromBlock: "latest", - }); - - logs.forEach((data) => { - log( - "ZetaChain", - `Event from onCrossChainCall: ${JSON.stringify(data)}` - ); - }); - // If message is empty, execute deposit - } else { - const depositTx = await protocolContracts.gatewayZEVM - .connect(fungibleModuleSigner) - .deposit(zrc20, amount, receiver, deployOpts); - - await depositTx.wait(); - } - } catch (e: any) { - logErr("ZetaChain", `Error depositing: ${e}`); - const revertOptions = args[5]; - await handleOnRevertEVM(revertOptions, e); - } + handleOnEVMDeposited({ + tss, + provider, + protocolContracts, + args, + deployer, + fungibleModuleSigner, + foreignCoins, + }); }); - const handleOnRevertEVM = async (revertOptions: any, err: any) => { - const callOnRevert = revertOptions[1]; - const revertAddress = revertOptions[0]; - const revertMessage = revertOptions[3]; - const revertContext = { - asset: ethers.ZeroAddress, - amount: 0, - revertMessage, - }; - if (callOnRevert) { - try { - log( - "EVM", - `Contract ${revertAddress} executing onRevert (context: ${JSON.stringify( - revertContext - )})` - ); - (tss as NonceManager).reset(); - const tx = await protocolContracts.gatewayEVM - .connect(tss) - .executeRevert(revertAddress, "0x", revertContext, deployOpts); - await tx.wait(); - log("EVM", "Gateway: successfully called onRevert"); - const logs = await provider.getLogs({ - address: revertAddress, - fromBlock: "latest", - }); - - logs.forEach((data) => { - log("EVM", `Event from onRevert: ${JSON.stringify(data)}`); - }); - } catch (e: any) { - logErr("EVM", `Gateway: Call onRevert failed: ${e}`); - } - } else { - logErr("EVM", `Tx reverted without callOnRevert: ${err}`); - } - }; - - const handleOnRevertZEVM = async (revertOptions: any, err: any) => { - const callOnRevert = revertOptions[1]; - const revertAddress = revertOptions[0]; - const revertMessage = revertOptions[3]; - const revertContext = { - asset: ethers.ZeroAddress, - amount: 0, - revertMessage, - }; - if (callOnRevert) { - log("ZetaChain", "Gateway: calling executeRevert"); - try { - (tss as NonceManager).reset(); - await protocolContracts.gatewayZEVM - .connect(tss) - .executeRevert(revertAddress, revertContext, deployOpts); - log("ZetaChain", "Gateway: Call onRevert success"); - } catch (e) { - log("ZetaChain", `Gateway: Call onRevert failed: ${e}`); - } - } else { - log("ZetaChain", "Tx reverted without callOnRevert: ", err); - } - }; - process.stdin.resume(); return { @@ -667,12 +343,10 @@ export const initLocalnet = async (port: number) => { gatewayZetaChain: protocolContracts.gatewayZEVM.target, zetaEVM: protocolContracts.testEVMZeta.target, zetaZetaChain: protocolContracts.wzeta.target, - zrc20ETHZetaChain: protocolContracts.zrc20Eth.target, - zrc20USDCZetaChain: protocolContracts.zrc20Usdc.target, - erc20UsdcEVM: protocolContracts.testERC20USDC.target, uniswapFactory: protocolContracts.uniswapFactoryInstance.target, uniswapRouter: protocolContracts.uniswapRouterInstance.target, fungibleModuleZetaChain: FUNGIBLE_MODULE_ADDRESS, + foreignCoins, sytemContractZetaChain: protocolContracts.systemContract.target, custodyEVM: protocolContracts.custodyEVM.target, tssEVM: await tss.getAddress(), diff --git a/packages/localnet/src/log.ts b/packages/localnet/src/log.ts new file mode 100644 index 0000000..0da53c6 --- /dev/null +++ b/packages/localnet/src/log.ts @@ -0,0 +1,12 @@ +import ansis from "ansis"; + +export const log = (chain: "EVM" | "ZetaChain", ...messages: string[]) => { + const color = chain === "ZetaChain" ? ansis.green : ansis.cyan; + const combinedMessage = messages.join(" "); + console.log(color(`[${ansis.bold(chain)}]: ${combinedMessage}`)); +}; + +export const logErr = (chain: "EVM" | "ZetaChain", ...messages: string[]) => { + const combinedMessage = messages.join(" "); + log(chain, ansis.red(combinedMessage)); +}; diff --git a/packages/tasks/src/localnet.ts b/packages/tasks/src/localnet.ts index 89da6b0..a58366b 100644 --- a/packages/tasks/src/localnet.ts +++ b/packages/tasks/src/localnet.ts @@ -30,9 +30,13 @@ EVM Contract Addresses Gateway EVM: ${addr.gatewayEVM} ERC-20 custody: ${addr.custodyEVM} TSS: ${addr.tssEVM} -ZETA: ${addr.zetaEVM} -ERC-20 USDC: ${addr.erc20UsdcEVM} -`); +ZETA: ${addr.zetaEVM}`); + + addr.foreignCoins + .filter((coin: any) => coin.asset !== "") + .forEach((coin: any) => { + console.log(ansis.cyan`ERC-20 ${coin.symbol}: ${coin.asset}`); + }); console.log(ansis.green` ZetaChain Contract Addresses @@ -40,11 +44,14 @@ ZetaChain Contract Addresses Gateway ZetaChain: ${addr.gatewayZetaChain} ZETA: ${addr.zetaZetaChain} -ZRC-20 ETH: ${addr.zrc20ETHZetaChain} -ZRC-20 USDC: ${addr.zrc20USDCZetaChain} Fungible module: ${addr.fungibleModuleZetaChain} -System contract: ${addr.sytemContractZetaChain} -`); +System contract: ${addr.sytemContractZetaChain}`); + + addr.foreignCoins.forEach((coin: any) => { + console.log( + ansis.green`ZRC-20 ${coin.symbol}: ${coin.zrc20_contract_address}` + ); + }); process.on("SIGINT", () => { console.log("\nReceived Ctrl-C, shutting down anvil...");