From 4fd776a514d505543fa01c58ae7986776c37471e Mon Sep 17 00:00:00 2001 From: Andres Martin Aiello <50411235+andresaiello@users.noreply.github.com> Date: Fri, 1 Mar 2024 12:49:05 -0300 Subject: [PATCH] feat: Create script to verify bytecode (#127) --- .../bytecode-checker/bytecode.constants.ts | 18 ++++ .../bytecode-checker/bytecode.helpers.ts | 94 ++++++++++++++++++ scripts/tools/bytecode-checker/bytecode.ts | 98 +++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 scripts/tools/bytecode-checker/bytecode.constants.ts create mode 100644 scripts/tools/bytecode-checker/bytecode.helpers.ts create mode 100644 scripts/tools/bytecode-checker/bytecode.ts diff --git a/scripts/tools/bytecode-checker/bytecode.constants.ts b/scripts/tools/bytecode-checker/bytecode.constants.ts new file mode 100644 index 00000000..0c6ed398 --- /dev/null +++ b/scripts/tools/bytecode-checker/bytecode.constants.ts @@ -0,0 +1,18 @@ +export const ETHERSCAN_API_ENDPOINT = "https://api.etherscan.io"; +export const BSC_ETHERSCAN_API_ENDPOINT = "https://api.bscscan.com"; +export const ZETA_NODE_ENDPOINT = "http://100.71.167.102:8545"; + +export const etherscanApiKey = process.env.ETHERSCAN_API_KEY; +export const bscEtherscanApiKey = process.env.BSCSCAN_API_KEY; + +export const ethConnectorAddress = "0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a"; +export const ethERC20CustodyAddress = "0x000001b91C19A31809e769110d35FAd2C15BCeA7"; +export const ethTokenAddress = "0xf091867EC603A6628eD83D274E835539D82e9cc8"; + +export const bscConnectorAddress = "0x000063A6e758D9e2f438d430108377564cf4077D"; +export const bscERC20CustodyAddress = "0x0000006Abbf11Ed0FabFD247f1F4d76A383cC395"; +export const bscTokenAddress = "0x0000028a2eB8346cd5c0267856aB7594B7a55308"; + +export const BTC_BTC = "0x13A0c5930C028511Dc02665E7285134B6d11A5f4"; +export const BNB_BSC = "0x48f80608B672DC30DC7e3dbBd0343c5F02C738Eb"; +export const ETH_ETH = "0xd97B1de3619ed2c6BEb3860147E30cA8A7dC9891"; diff --git a/scripts/tools/bytecode-checker/bytecode.helpers.ts b/scripts/tools/bytecode-checker/bytecode.helpers.ts new file mode 100644 index 00000000..481b94af --- /dev/null +++ b/scripts/tools/bytecode-checker/bytecode.helpers.ts @@ -0,0 +1,94 @@ +const fetch = require("node-fetch"); +const fs = require("fs"); +const path = require("path"); +const { ethers } = require("ethers"); +import { BigNumber } from "ethers"; + +import { + BSC_ETHERSCAN_API_ENDPOINT, + bscEtherscanApiKey, + ETHERSCAN_API_ENDPOINT, + etherscanApiKey, + ZETA_NODE_ENDPOINT, +} from "./bytecode.constants"; + +export const encodeNumber = (weiValue: BigNumber) => { + return ethers.utils.hexZeroPad(weiValue.toHexString(), 32).replace("0x", ""); +}; + +export const encodeAddress = (address: string) => { + return ethers.utils.hexZeroPad(address, 32).replace("0x", ""); +}; + +export const compareBytecode = (bytecodeA: string, bytecodeB: string) => { + if (bytecodeA === bytecodeB) { + console.log("Bytecode matches!"); + } else { + console.error("Bytecode doesn't match!"); + } +}; + +export const getEtherscanBytecode = async (network: "bsc" | "eth", contractAddress: string) => { + const endpoint = network === "bsc" ? BSC_ETHERSCAN_API_ENDPOINT : ETHERSCAN_API_ENDPOINT; + const apiKey = network === "bsc" ? bscEtherscanApiKey : etherscanApiKey; + const etherscanApiUrl = `${endpoint}/api?module=proxy&action=eth_getCode&address=${contractAddress}&apikey=${apiKey}`; + + try { + const response = await fetch(etherscanApiUrl); + const data = await response.json(); + const bytecode = data.result; + return bytecode; + } catch (error) { + console.error("Error fetching bytecode:", error); + } +}; + +// @dev: helper to find differences between two bytecodes when there's a mismatch +export const findDiff = (codeA: string, codeB: string) => { + console.log("Lengths:", codeA.length, codeB.length); + for (let i = 0; i < codeA.length; i++) { + if (codeA[i] !== codeB[i]) { + console.log(i, codeA.substring(i - 20, i + 40), codeB.substring(i - 20, i + 40)); + } + } +}; + +export const getZetaNodeBytecode = async (contractAddress: string) => { + try { + const provider = new ethers.providers.JsonRpcProvider(ZETA_NODE_ENDPOINT); + const bytecode = await provider.getCode(contractAddress); + return bytecode; + } catch (error) { + console.error("Error fetching bytecode:", error); + console.error("Please make sure you are connected to tailscale."); + } +}; + +export const removeImmutableAddress = (bytecode: string, pattern: string) => { + const replacement = encodeAddress(ethers.constants.AddressZero); + const regex = new RegExp(pattern, "gi"); + + bytecode = bytecode.replace(regex, replacement); + return bytecode; +}; + +export const removeImmutableNumber = (bytecode: string, pattern: string) => { + const replacement = encodeNumber(BigNumber.from("0")); + const regex = new RegExp(pattern, "gi"); + + bytecode = bytecode.replace(regex, replacement); + return bytecode; +}; + +export const getDeployedBytecode = async (contract: string, kind: "evm" | "zevm") => { + try { + const filePath = path.join(__dirname, `../../../artifacts/contracts/${kind}/${contract}.json`); + const fileContent = fs.readFileSync(filePath, "utf8"); + const jsonContent = JSON.parse(fileContent); + const deployedBytecode = jsonContent.deployedBytecode; + + return deployedBytecode; + } catch (error) { + console.error("Error reading the file or parsing JSON:", error); + } +}; diff --git a/scripts/tools/bytecode-checker/bytecode.ts b/scripts/tools/bytecode-checker/bytecode.ts new file mode 100644 index 00000000..2a667cfd --- /dev/null +++ b/scripts/tools/bytecode-checker/bytecode.ts @@ -0,0 +1,98 @@ +import { BigNumber } from "ethers"; +import { parseEther } from "ethers/lib/utils"; + +import { + BNB_BSC, + bscConnectorAddress, + bscERC20CustodyAddress, + bscTokenAddress, + BTC_BTC, + ETH_ETH, + ethConnectorAddress, + ethERC20CustodyAddress, + ethTokenAddress, +} from "./bytecode.constants"; +import { + compareBytecode, + encodeAddress, + encodeNumber, + getDeployedBytecode, + getEtherscanBytecode, + getZetaNodeBytecode, + removeImmutableAddress, + removeImmutableNumber, +} from "./bytecode.helpers"; + +const checkEthConnectorBytecode = async () => { + const remoteBytecode = await getEtherscanBytecode("eth", ethConnectorAddress); + const cleanRemoteBytecode = removeImmutableAddress(remoteBytecode, encodeAddress(ethTokenAddress)); + const deployedBytecode = await getDeployedBytecode("ZetaConnector.eth.sol/ZetaConnectorEth", "evm"); + compareBytecode(cleanRemoteBytecode, deployedBytecode); +}; + +const checkEthCustodyBytecode = async () => { + const remoteBytecode = await getEtherscanBytecode("eth", ethERC20CustodyAddress); + let cleanRemoteBytecode = removeImmutableAddress(remoteBytecode, encodeAddress(ethTokenAddress)); + cleanRemoteBytecode = removeImmutableNumber(cleanRemoteBytecode, encodeNumber(parseEther("1000"))); // zetaMaxFee + const deployedBytecode = await getDeployedBytecode("ERC20Custody.sol/ERC20Custody", "evm"); + compareBytecode(cleanRemoteBytecode, deployedBytecode); +}; + +const checkBscConnectorBytecode = async () => { + const remoteBytecode = await getEtherscanBytecode("bsc", bscConnectorAddress); + const cleanRemoteBytecode = removeImmutableAddress(remoteBytecode, encodeAddress(bscTokenAddress)); + const deployedBytecode = await getDeployedBytecode("ZetaConnector.non-eth.sol/ZetaConnectorNonEth", "evm"); + compareBytecode(cleanRemoteBytecode, deployedBytecode); +}; + +const checkBscCustodyBytecode = async () => { + const remoteBytecode = await getEtherscanBytecode("bsc", bscERC20CustodyAddress); + let cleanRemoteBytecode = removeImmutableAddress(remoteBytecode, encodeAddress(bscTokenAddress)); + cleanRemoteBytecode = removeImmutableNumber(cleanRemoteBytecode, encodeNumber(parseEther("1000"))); // zetaMaxFee + const deployedBytecode = await getDeployedBytecode("ERC20Custody.sol/ERC20Custody", "evm"); + compareBytecode(cleanRemoteBytecode, deployedBytecode); +}; + +const checkZRC20ETHBytecode = async () => { + const remoteBytecode = await getZetaNodeBytecode(ETH_ETH); + let cleanRemoteBytecode = removeImmutableNumber(remoteBytecode, encodeNumber(BigNumber.from("1"))); // ETH CHAIN ID + cleanRemoteBytecode = removeImmutableNumber(cleanRemoteBytecode, encodeNumber(BigNumber.from("1"))); // Gas COIN TYPE + const deployedBytecode = await getDeployedBytecode("ZRC20.sol/ZRC20", "zevm"); + compareBytecode(cleanRemoteBytecode, deployedBytecode); +}; + +const checkZRC20BTCBytecode = async () => { + const remoteBytecode = await getZetaNodeBytecode(BTC_BTC); + let cleanRemoteBytecode = removeImmutableNumber(remoteBytecode, encodeNumber(BigNumber.from("8332"))); // BTC CHAIN ID + cleanRemoteBytecode = removeImmutableNumber(cleanRemoteBytecode, encodeNumber(BigNumber.from("1"))); // Gas COIN TYPE + const deployedBytecode = await getDeployedBytecode("ZRC20.sol/ZRC20", "zevm"); + compareBytecode(cleanRemoteBytecode, deployedBytecode); +}; + +const checkZRC20BSCBytecode = async () => { + const remoteBytecode = await getZetaNodeBytecode(BNB_BSC); + let cleanRemoteBytecode = removeImmutableNumber(remoteBytecode, encodeNumber(BigNumber.from("56"))); // BSC CHAIN ID + cleanRemoteBytecode = removeImmutableNumber(cleanRemoteBytecode, encodeNumber(BigNumber.from("1"))); // Gas COIN TYPE + const deployedBytecode = await getDeployedBytecode("ZRC20.sol/ZRC20", "zevm"); + compareBytecode(cleanRemoteBytecode, deployedBytecode); +}; + +const checkBytecode = async () => { + // ETH + await checkEthConnectorBytecode(); + await checkEthCustodyBytecode(); + // BSC + await checkBscConnectorBytecode(); + await checkBscCustodyBytecode(); + // ZEVM + await checkZRC20ETHBytecode(); + await checkZRC20BTCBytecode(); + await checkZRC20BSCBytecode(); +}; + +checkBytecode() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + });