From 09c1c08ffa7c931377ddb8c66068cbe8624b0560 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Sun, 7 Apr 2024 18:48:39 +0530 Subject: [PATCH 1/7] allow networks more networks for proxy contracts --- .../pages/[contractAddress]/[network].tsx | 27 ++++++++++++++----- packages/nextjs/pages/index.tsx | 27 ++++++++++++++----- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index c0867360..22a19267 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -1,10 +1,10 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { AlchemyProvider } from "@ethersproject/providers"; +import { JsonRpcProvider } from "@ethersproject/providers"; import detectProxyTarget from "evm-proxy-detection"; import { ParsedUrlQuery } from "querystring"; -import { Abi, isAddress } from "viem"; +import { Abi, extractChain, isAddress } from "viem"; import * as chains from "viem/chains"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { MetaHeader } from "~~/components/MetaHeader"; @@ -24,6 +24,8 @@ type ContractData = { address: string; }; +type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; + const ContractDetailPage = () => { const router = useRouter(); const { contractAddress, network } = router.query as ParsedQueryContractDetailsPage; @@ -77,10 +79,23 @@ const ContractDetailPage = () => { } try { - const alchemyProvider = new AlchemyProvider(undefined, scaffoldConfig.alchemyApiKey); - const requestFunc = ({ method, params }: { method: string; params: any }) => - alchemyProvider.send(method, params); - const implementationAddress = await detectProxyTarget(contractAddress, requestFunc); + const chain = extractChain({ + id: parseInt(network) as AllowedNetwork, + chains: Object.values(scaffoldConfig.targetNetworks), + }); + // @ts-expect-error this might be present or might not be + const alchmeyRPCURL = chain.rpcUrls?.alchemy?.http[0]; + let implementationAddress = undefined; + if (alchmeyRPCURL) { + const alchemyProvider = new JsonRpcProvider( + `${alchmeyRPCURL}/${scaffoldConfig.alchemyApiKey}`, + parseInt(network), + ); + const requestFunc = ({ method, params }: { method: string; params: any }) => + alchemyProvider.send(method, params); + implementationAddress = await detectProxyTarget(contractAddress, requestFunc); + } + if (implementationAddress) { setImplementationAddress(implementationAddress); } diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 5141d71f..4d12c041 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -2,10 +2,10 @@ import { useEffect, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import { AlchemyProvider } from "@ethersproject/providers"; +import { JsonRpcProvider } from "@ethersproject/providers"; import detectProxyTarget from "evm-proxy-detection"; import type { NextPage } from "next"; -import { Address, isAddress } from "viem"; +import { Address, extractChain, isAddress } from "viem"; import { usePublicClient } from "wagmi"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniFooter } from "~~/components/MiniFooter"; @@ -21,6 +21,8 @@ enum TabName { addressAbi, } +type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; + const tabValues = Object.values(TabName) as TabName[]; const networks = getTargetNetworks(); @@ -35,9 +37,6 @@ const Home: NextPage = () => { const [isCheckingContractAddress, setIsCheckingContractAddress] = useState(false); const [isContract, setIsContract] = useState(false); - const alchemyProvider = new AlchemyProvider(undefined, scaffoldConfig.alchemyApiKey); - const requestFunc = ({ method, params }: { method: string; params: any }) => alchemyProvider.send(method, params); - const publicClient = usePublicClient({ chainId: parseInt(network), }); @@ -56,7 +55,23 @@ const Home: NextPage = () => { const fetchContractAbi = async () => { setIsFetchingAbi(true); try { - const implementationAddress = await detectProxyTarget(verifiedContractAddress, requestFunc); + const chain = extractChain({ + id: parseInt(network) as AllowedNetwork, + chains: Object.values(scaffoldConfig.targetNetworks), + }); + // @ts-expect-error this might be present or might not be + const alchmeyRPCURL = chain.rpcUrls?.alchemy?.http[0]; + let implementationAddress = undefined; + if (alchmeyRPCURL) { + const alchemyProvider = new JsonRpcProvider( + `${alchmeyRPCURL}/${scaffoldConfig.alchemyApiKey}`, + parseInt(network), + ); + const requestFunc = ({ method, params }: { method: string; params: any }) => + alchemyProvider.send(method, params); + implementationAddress = await detectProxyTarget(verifiedContractAddress, requestFunc); + } + if (implementationAddress) { setImplementationAddress(implementationAddress); } From 84e74cd061bca2c5f084bf17c1f85c3dfed01090 Mon Sep 17 00:00:00 2001 From: port <108868128+portdeveloper@users.noreply.github.com> Date: Sun, 7 Apr 2024 17:38:48 +0300 Subject: [PATCH 2/7] Use viem to detect proxy contracts --- .../pages/[contractAddress]/[network].tsx | 32 ++--- packages/nextjs/pages/index.tsx | 25 +--- .../nextjs/utils/abi-ninja/proxyContracts.ts | 116 ++++++++++++++++++ 3 files changed, 128 insertions(+), 45 deletions(-) create mode 100644 packages/nextjs/utils/abi-ninja/proxyContracts.ts diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index 22a19267..0f3240f1 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -1,18 +1,17 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { JsonRpcProvider } from "@ethersproject/providers"; -import detectProxyTarget from "evm-proxy-detection"; import { ParsedUrlQuery } from "querystring"; -import { Abi, extractChain, isAddress } from "viem"; +import { Abi, isAddress } from "viem"; import * as chains from "viem/chains"; +import { usePublicClient } from "wagmi"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniHeader } from "~~/components/MiniHeader"; import { ContractUI } from "~~/components/scaffold-eth"; -import scaffoldConfig from "~~/scaffold.config"; import { useAbiNinjaState } from "~~/services/store/store"; import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan } from "~~/utils/abi"; +import { detectProxyTarget } from "~~/utils/abi-ninja/proxyContracts"; interface ParsedQueryContractDetailsPage extends ParsedUrlQuery { contractAddress: string; @@ -24,8 +23,6 @@ type ContractData = { address: string; }; -type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; - const ContractDetailPage = () => { const router = useRouter(); const { contractAddress, network } = router.query as ParsedQueryContractDetailsPage; @@ -45,6 +42,10 @@ const ContractDetailPage = () => { setImplementationAddress: state.setImplementationAddress, })); + const publicClient = usePublicClient({ + chainId: parseInt(network), + }); + const getNetworkName = (chainId: number) => { const chain = Object.values(chains).find(chain => chain.id === chainId); return chain ? chain.name : "Unknown Network"; @@ -79,22 +80,7 @@ const ContractDetailPage = () => { } try { - const chain = extractChain({ - id: parseInt(network) as AllowedNetwork, - chains: Object.values(scaffoldConfig.targetNetworks), - }); - // @ts-expect-error this might be present or might not be - const alchmeyRPCURL = chain.rpcUrls?.alchemy?.http[0]; - let implementationAddress = undefined; - if (alchmeyRPCURL) { - const alchemyProvider = new JsonRpcProvider( - `${alchmeyRPCURL}/${scaffoldConfig.alchemyApiKey}`, - parseInt(network), - ); - const requestFunc = ({ method, params }: { method: string; params: any }) => - alchemyProvider.send(method, params); - implementationAddress = await detectProxyTarget(contractAddress, requestFunc); - } + const implementationAddress = await detectProxyTarget(contractAddress, publicClient); if (implementationAddress) { setImplementationAddress(implementationAddress); @@ -129,7 +115,7 @@ const ContractDetailPage = () => { } } } - }, [contractAddress, network, storedAbi, setMainChainId, setImplementationAddress]); + }, [contractAddress, network, storedAbi, setMainChainId, setImplementationAddress, publicClient]); return ( <> diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index 4d12c041..c6fac2d4 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -2,18 +2,16 @@ import { useEffect, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import { JsonRpcProvider } from "@ethersproject/providers"; -import detectProxyTarget from "evm-proxy-detection"; import type { NextPage } from "next"; -import { Address, extractChain, isAddress } from "viem"; +import { Address, isAddress } from "viem"; import { usePublicClient } from "wagmi"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniFooter } from "~~/components/MiniFooter"; import { NetworksDropdown } from "~~/components/NetworksDropdown"; import { AddressInput, InputBase } from "~~/components/scaffold-eth"; -import scaffoldConfig from "~~/scaffold.config"; import { useAbiNinjaState } from "~~/services/store/store"; import { fetchContractABIFromAnyABI, fetchContractABIFromEtherscan, parseAndCorrectJSON } from "~~/utils/abi"; +import { detectProxyTarget } from "~~/utils/abi-ninja/proxyContracts"; import { getTargetNetworks, notification } from "~~/utils/scaffold-eth"; enum TabName { @@ -21,8 +19,6 @@ enum TabName { addressAbi, } -type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; - const tabValues = Object.values(TabName) as TabName[]; const networks = getTargetNetworks(); @@ -55,22 +51,7 @@ const Home: NextPage = () => { const fetchContractAbi = async () => { setIsFetchingAbi(true); try { - const chain = extractChain({ - id: parseInt(network) as AllowedNetwork, - chains: Object.values(scaffoldConfig.targetNetworks), - }); - // @ts-expect-error this might be present or might not be - const alchmeyRPCURL = chain.rpcUrls?.alchemy?.http[0]; - let implementationAddress = undefined; - if (alchmeyRPCURL) { - const alchemyProvider = new JsonRpcProvider( - `${alchmeyRPCURL}/${scaffoldConfig.alchemyApiKey}`, - parseInt(network), - ); - const requestFunc = ({ method, params }: { method: string; params: any }) => - alchemyProvider.send(method, params); - implementationAddress = await detectProxyTarget(verifiedContractAddress, requestFunc); - } + const implementationAddress = await detectProxyTarget(verifiedContractAddress, publicClient); if (implementationAddress) { setImplementationAddress(implementationAddress); diff --git a/packages/nextjs/utils/abi-ninja/proxyContracts.ts b/packages/nextjs/utils/abi-ninja/proxyContracts.ts new file mode 100644 index 00000000..d78352a9 --- /dev/null +++ b/packages/nextjs/utils/abi-ninja/proxyContracts.ts @@ -0,0 +1,116 @@ +const EIP_1967_LOGIC_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; +const EIP_1967_BEACON_SLOT = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"; +// const OPEN_ZEPPELIN_IMPLEMENTATION_SLOT = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"; +const EIP_1822_LOGIC_SLOT = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"; +const EIP_1167_BEACON_METHODS = [ + "0x5c60da1b00000000000000000000000000000000000000000000000000000000", + "0xda52571600000000000000000000000000000000000000000000000000000000", +]; +const EIP_897_INTERFACE = ["0x5c60da1b00000000000000000000000000000000000000000000000000000000"]; +const GNOSIS_SAFE_PROXY_INTERFACE = ["0xa619486e00000000000000000000000000000000000000000000000000000000"]; +const COMPTROLLER_PROXY_INTERFACE = ["0xbb82aa5e00000000000000000000000000000000000000000000000000000000"]; + +const readAddress = (value: string): string => { + if (typeof value !== "string" || value === "0x") { + throw new Error(`Invalid address value: ${value}`); + } + const address = value.length === 66 ? "0x" + value.slice(-40) : value; + const zeroAddress = "0x" + "0".repeat(40); + if (address === zeroAddress) { + throw new Error("Empty address"); + } + return address; +}; + +const EIP_1167_BYTECODE_PREFIX = "0x363d3d373d3d3d363d"; +const EIP_1167_BYTECODE_SUFFIX = "57fd5bf3"; + +export const parse1167Bytecode = (bytecode: unknown): string => { + if (typeof bytecode !== "string" || !bytecode.startsWith(EIP_1167_BYTECODE_PREFIX)) { + throw new Error("Not an EIP-1167 bytecode"); + } + + // detect length of address (20 bytes non-optimized, 0 < N < 20 bytes for vanity addresses) + const pushNHex = bytecode.substring(EIP_1167_BYTECODE_PREFIX.length, EIP_1167_BYTECODE_PREFIX.length + 2); + // push1 ... push20 use opcodes 0x60 ... 0x73 + const addressLength = parseInt(pushNHex, 16) - 0x5f; + + if (addressLength < 1 || addressLength > 20) { + throw new Error("Not an EIP-1167 bytecode"); + } + + const addressFromBytecode = bytecode.substring( + EIP_1167_BYTECODE_PREFIX.length + 2, + EIP_1167_BYTECODE_PREFIX.length + 2 + addressLength * 2, // address length is in bytes, 2 hex chars make up 1 byte + ); + + const SUFFIX_OFFSET_FROM_ADDRESS_END = 22; + if ( + !bytecode + .substring(EIP_1167_BYTECODE_PREFIX.length + 2 + addressLength * 2 + SUFFIX_OFFSET_FROM_ADDRESS_END) + .startsWith(EIP_1167_BYTECODE_SUFFIX) + ) { + throw new Error("Not an EIP-1167 bytecode"); + } + + // padStart is needed for vanity addresses + return `0x${addressFromBytecode.padStart(40, "0")}`; +}; + +export const detectProxyTarget = async (proxyAddress: string, client: any): Promise => { + try { + return await Promise.any([ + (async () => { + const bytecode = await client.getBytecode({ address: proxyAddress }); + return parse1167Bytecode(bytecode); + })(), + (async () => { + const logicAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1967_LOGIC_SLOT }); + return readAddress(logicAddress); + })(), + (async () => { + const beaconAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1967_BEACON_SLOT }); + const resolvedBeaconAddress = readAddress(beaconAddress); + for (const method of EIP_1167_BEACON_METHODS) { + try { + const data = await client.call({ data: method as `0x${string}`, to: resolvedBeaconAddress }); + return readAddress(data.data); + } catch { + // Ignore + } + } + throw new Error("Beacon method calls failed"); + })(), + ]); + } catch (error) { + try { + return await Promise.any([ + (async () => { + const logicAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1822_LOGIC_SLOT }); + return readAddress(logicAddress); + })(), + (async () => { + const { data } = await client.call({ data: EIP_897_INTERFACE[0] as `0x${string}`, to: proxyAddress }); + return readAddress(data); + })(), + (async () => { + const { data } = await client.call({ + data: GNOSIS_SAFE_PROXY_INTERFACE[0] as `0x${string}`, + to: proxyAddress, + }); + return readAddress(data); + })(), + (async () => { + const { data } = await client.call({ + data: COMPTROLLER_PROXY_INTERFACE[0] as `0x${string}`, + to: proxyAddress, + }); + return readAddress(data); + })(), + ]); + } catch (fallbackError) { + // @todo can't return null? + return null; + } + } +}; From 4204e5196ed09c3f09c396bbd509ea75d5f46ae6 Mon Sep 17 00:00:00 2001 From: port <108868128+portdeveloper@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:13:24 +0300 Subject: [PATCH 3/7] Remove unused import + type in index.tsx and [network].tsx files --- packages/nextjs/pages/[contractAddress]/[network].tsx | 4 +--- packages/nextjs/pages/index.tsx | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/nextjs/pages/[contractAddress]/[network].tsx b/packages/nextjs/pages/[contractAddress]/[network].tsx index 8495c61f..0f3240f1 100644 --- a/packages/nextjs/pages/[contractAddress]/[network].tsx +++ b/packages/nextjs/pages/[contractAddress]/[network].tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import { ParsedUrlQuery } from "querystring"; -import { Abi, extractChain, isAddress } from "viem"; +import { Abi, isAddress } from "viem"; import * as chains from "viem/chains"; import { usePublicClient } from "wagmi"; import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; @@ -23,8 +23,6 @@ type ContractData = { address: string; }; -type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; - const ContractDetailPage = () => { const router = useRouter(); const { contractAddress, network } = router.query as ParsedQueryContractDetailsPage; diff --git a/packages/nextjs/pages/index.tsx b/packages/nextjs/pages/index.tsx index c69fdf7e..c6fac2d4 100644 --- a/packages/nextjs/pages/index.tsx +++ b/packages/nextjs/pages/index.tsx @@ -3,7 +3,7 @@ import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import type { NextPage } from "next"; -import { Address, extractChain, isAddress } from "viem"; +import { Address, isAddress } from "viem"; import { usePublicClient } from "wagmi"; import { MetaHeader } from "~~/components/MetaHeader"; import { MiniFooter } from "~~/components/MiniFooter"; @@ -19,8 +19,6 @@ enum TabName { addressAbi, } -type AllowedNetwork = (typeof scaffoldConfig.targetNetworks)[number]["id"]; - const tabValues = Object.values(TabName) as TabName[]; const networks = getTargetNetworks(); From 1b89e2c8b722299bcc9fd771cef925455c02b2f5 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 12 Apr 2024 18:24:07 +0530 Subject: [PATCH 4/7] remove assertion of 0xString and mark variables with as const --- .../nextjs/utils/abi-ninja/proxyContracts.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/nextjs/utils/abi-ninja/proxyContracts.ts b/packages/nextjs/utils/abi-ninja/proxyContracts.ts index d78352a9..106523ae 100644 --- a/packages/nextjs/utils/abi-ninja/proxyContracts.ts +++ b/packages/nextjs/utils/abi-ninja/proxyContracts.ts @@ -1,14 +1,14 @@ -const EIP_1967_LOGIC_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; -const EIP_1967_BEACON_SLOT = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"; +const EIP_1967_LOGIC_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" as const; +const EIP_1967_BEACON_SLOT = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" as const; // const OPEN_ZEPPELIN_IMPLEMENTATION_SLOT = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"; -const EIP_1822_LOGIC_SLOT = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"; +const EIP_1822_LOGIC_SLOT = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" as const; const EIP_1167_BEACON_METHODS = [ "0x5c60da1b00000000000000000000000000000000000000000000000000000000", "0xda52571600000000000000000000000000000000000000000000000000000000", -]; -const EIP_897_INTERFACE = ["0x5c60da1b00000000000000000000000000000000000000000000000000000000"]; -const GNOSIS_SAFE_PROXY_INTERFACE = ["0xa619486e00000000000000000000000000000000000000000000000000000000"]; -const COMPTROLLER_PROXY_INTERFACE = ["0xbb82aa5e00000000000000000000000000000000000000000000000000000000"]; +] as const; +const EIP_897_INTERFACE = ["0x5c60da1b00000000000000000000000000000000000000000000000000000000"] as const; +const GNOSIS_SAFE_PROXY_INTERFACE = ["0xa619486e00000000000000000000000000000000000000000000000000000000"] as const; +const COMPTROLLER_PROXY_INTERFACE = ["0xbb82aa5e00000000000000000000000000000000000000000000000000000000"] as const; const readAddress = (value: string): string => { if (typeof value !== "string" || value === "0x") { @@ -73,7 +73,7 @@ export const detectProxyTarget = async (proxyAddress: string, client: any): Prom const resolvedBeaconAddress = readAddress(beaconAddress); for (const method of EIP_1167_BEACON_METHODS) { try { - const data = await client.call({ data: method as `0x${string}`, to: resolvedBeaconAddress }); + const data = await client.call({ data: method, to: resolvedBeaconAddress }); return readAddress(data.data); } catch { // Ignore @@ -90,19 +90,19 @@ export const detectProxyTarget = async (proxyAddress: string, client: any): Prom return readAddress(logicAddress); })(), (async () => { - const { data } = await client.call({ data: EIP_897_INTERFACE[0] as `0x${string}`, to: proxyAddress }); + const { data } = await client.call({ data: EIP_897_INTERFACE[0], to: proxyAddress }); return readAddress(data); })(), (async () => { const { data } = await client.call({ - data: GNOSIS_SAFE_PROXY_INTERFACE[0] as `0x${string}`, + data: GNOSIS_SAFE_PROXY_INTERFACE[0], to: proxyAddress, }); return readAddress(data); })(), (async () => { const { data } = await client.call({ - data: COMPTROLLER_PROXY_INTERFACE[0] as `0x${string}`, + data: COMPTROLLER_PROXY_INTERFACE[0], to: proxyAddress, }); return readAddress(data); From 49cf3ac2996862454a6421968e46a29e0e4c21f7 Mon Sep 17 00:00:00 2001 From: Shiv Bhonde Date: Fri, 12 Apr 2024 18:25:37 +0530 Subject: [PATCH 5/7] type client from any to PublicClient --- packages/nextjs/utils/abi-ninja/proxyContracts.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/nextjs/utils/abi-ninja/proxyContracts.ts b/packages/nextjs/utils/abi-ninja/proxyContracts.ts index 106523ae..6c88ed7c 100644 --- a/packages/nextjs/utils/abi-ninja/proxyContracts.ts +++ b/packages/nextjs/utils/abi-ninja/proxyContracts.ts @@ -1,3 +1,5 @@ +import { PublicClient } from "wagmi"; + const EIP_1967_LOGIC_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" as const; const EIP_1967_BEACON_SLOT = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" as const; // const OPEN_ZEPPELIN_IMPLEMENTATION_SLOT = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"; @@ -10,7 +12,7 @@ const EIP_897_INTERFACE = ["0x5c60da1b000000000000000000000000000000000000000000 const GNOSIS_SAFE_PROXY_INTERFACE = ["0xa619486e00000000000000000000000000000000000000000000000000000000"] as const; const COMPTROLLER_PROXY_INTERFACE = ["0xbb82aa5e00000000000000000000000000000000000000000000000000000000"] as const; -const readAddress = (value: string): string => { +const readAddress = (value: string | undefined) => { if (typeof value !== "string" || value === "0x") { throw new Error(`Invalid address value: ${value}`); } @@ -57,7 +59,7 @@ export const parse1167Bytecode = (bytecode: unknown): string => { return `0x${addressFromBytecode.padStart(40, "0")}`; }; -export const detectProxyTarget = async (proxyAddress: string, client: any): Promise => { +export const detectProxyTarget = async (proxyAddress: string, client: PublicClient): Promise => { try { return await Promise.any([ (async () => { From 0b8c36d6588272d305b7244abf2418baa36af175 Mon Sep 17 00:00:00 2001 From: port <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 13 Apr 2024 13:04:24 +0300 Subject: [PATCH 6/7] Tidy up detectProxyTarget logic --- .../nextjs/utils/abi-ninja/proxyContracts.ts | 100 +++++++++--------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/packages/nextjs/utils/abi-ninja/proxyContracts.ts b/packages/nextjs/utils/abi-ninja/proxyContracts.ts index 6c88ed7c..3d66f06c 100644 --- a/packages/nextjs/utils/abi-ninja/proxyContracts.ts +++ b/packages/nextjs/utils/abi-ninja/proxyContracts.ts @@ -59,59 +59,57 @@ export const parse1167Bytecode = (bytecode: unknown): string => { return `0x${addressFromBytecode.padStart(40, "0")}`; }; -export const detectProxyTarget = async (proxyAddress: string, client: PublicClient): Promise => { +export const detectProxyTarget = async (proxyAddress: string, client: PublicClient) => { + const detectUsingBytecode = async () => { + const bytecode = await client.getBytecode({ address: proxyAddress }); + return parse1167Bytecode(bytecode); + }; + + const detectUsingEIP1967LogicSlot = async () => { + const logicAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1967_LOGIC_SLOT }); + return readAddress(logicAddress); + }; + + const detectUsingEIP1967BeaconSlot = async () => { + const beaconAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1967_BEACON_SLOT }); + const resolvedBeaconAddress = readAddress(beaconAddress); + for (const method of EIP_1167_BEACON_METHODS) { + try { + const data = await client.call({ data: method as `0x${string}`, to: resolvedBeaconAddress }); + return readAddress(data.data); + } catch { + // Ignore individual beacon method call failures + } + } + throw new Error("Beacon method calls failed"); + }; + + const detectionMethods = [detectUsingBytecode, detectUsingEIP1967LogicSlot, detectUsingEIP1967BeaconSlot]; + try { - return await Promise.any([ - (async () => { - const bytecode = await client.getBytecode({ address: proxyAddress }); - return parse1167Bytecode(bytecode); - })(), - (async () => { - const logicAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1967_LOGIC_SLOT }); - return readAddress(logicAddress); - })(), - (async () => { - const beaconAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1967_BEACON_SLOT }); - const resolvedBeaconAddress = readAddress(beaconAddress); - for (const method of EIP_1167_BEACON_METHODS) { - try { - const data = await client.call({ data: method, to: resolvedBeaconAddress }); - return readAddress(data.data); - } catch { - // Ignore - } - } - throw new Error("Beacon method calls failed"); - })(), - ]); - } catch (error) { + return await Promise.any(detectionMethods.map(method => method())); + } catch (primaryError) { + const detectUsingEIP1822LogicSlot = async () => { + const logicAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1822_LOGIC_SLOT }); + return readAddress(logicAddress); + }; + + const detectUsingInterfaceCalls = async (data: `0x${string}`) => { + const { data: resultData } = await client.call({ data, to: proxyAddress }); + return readAddress(resultData); + }; + + const nextDetectionMethods = [ + detectUsingEIP1822LogicSlot, + () => detectUsingInterfaceCalls(EIP_897_INTERFACE[0]), + () => detectUsingInterfaceCalls(GNOSIS_SAFE_PROXY_INTERFACE[0]), + () => detectUsingInterfaceCalls(COMPTROLLER_PROXY_INTERFACE[0]), + ]; + try { - return await Promise.any([ - (async () => { - const logicAddress = await client.getStorageAt({ address: proxyAddress, slot: EIP_1822_LOGIC_SLOT }); - return readAddress(logicAddress); - })(), - (async () => { - const { data } = await client.call({ data: EIP_897_INTERFACE[0], to: proxyAddress }); - return readAddress(data); - })(), - (async () => { - const { data } = await client.call({ - data: GNOSIS_SAFE_PROXY_INTERFACE[0], - to: proxyAddress, - }); - return readAddress(data); - })(), - (async () => { - const { data } = await client.call({ - data: COMPTROLLER_PROXY_INTERFACE[0], - to: proxyAddress, - }); - return readAddress(data); - })(), - ]); - } catch (fallbackError) { - // @todo can't return null? + return await Promise.any(nextDetectionMethods.map(method => method())); + } catch (finalError) { + console.error("All detection methods failed:", finalError); return null; } } From 47227211e8ae0f37b308557e6909cba09b3ec7ef Mon Sep 17 00:00:00 2001 From: port <108868128+portdeveloper@users.noreply.github.com> Date: Sat, 13 Apr 2024 13:06:30 +0300 Subject: [PATCH 7/7] Remove evm-proxy-detection package --- packages/nextjs/package.json | 1 - yarn.lock | 8 -------- 2 files changed, 9 deletions(-) diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 362de183..83609f16 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -21,7 +21,6 @@ "@uniswap/v2-sdk": "^3.0.1", "blo": "^1.0.1", "daisyui": "^4.4.19", - "evm-proxy-detection": "^1.2.0", "next": "13.3.4", "next-plausible": "^3.12.0", "nextjs-progressbar": "^0.0.16", diff --git a/yarn.lock b/yarn.lock index 7c5dbd4f..7407a364 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1370,7 +1370,6 @@ __metadata: eslint-config-next: ^13.1.6 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 - evm-proxy-detection: ^1.2.0 next: 13.3.4 next-plausible: ^3.12.0 nextjs-progressbar: ^0.0.16 @@ -4877,13 +4876,6 @@ __metadata: languageName: node linkType: hard -"evm-proxy-detection@npm:^1.2.0": - version: 1.2.0 - resolution: "evm-proxy-detection@npm:1.2.0" - checksum: d9996cbcd22eadd0b1209116d1f5c90ebf063edfe08c71c2f92372040dc004248ab280887a2f6d968d3e4963f40909f7f4cece584992e8e1f6ea136d6e18f2ee - languageName: node - linkType: hard - "execa@npm:^6.1.0": version: 6.1.0 resolution: "execa@npm:6.1.0"