From 32e93d25ce29cfcf15b953d94d3acdb83f26c7ca Mon Sep 17 00:00:00 2001 From: Jan-Felix Date: Fri, 26 Apr 2024 21:37:00 +0200 Subject: [PATCH 1/5] BREAKING: return additional information about the proxy - proxy type - immutability --- .cspell.json | 1 + README.md | 47 ++++- package.json | 4 +- src/eip1167.ts | 45 +++++ src/index.ts | 173 ++++++++++-------- src/types.ts | 28 ++- ...roxyTarget.spec.ts => detectProxy.spec.ts} | 24 +-- 7 files changed, 217 insertions(+), 105 deletions(-) create mode 100644 src/eip1167.ts rename test/{detectProxyTarget.spec.ts => detectProxy.spec.ts} (87%) diff --git a/.cspell.json b/.cspell.json index 4731cd3..3dc49da 100644 --- a/.cspell.json +++ b/.cspell.json @@ -4,6 +4,7 @@ "language": "en", "words": [ "bignumber", + "checksummed", "ethersproject", "keccak", "PROXIABLE", diff --git a/README.md b/README.md index dcb00e2..f3b00ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # evm-proxy-detection -Detect proxy contracts and their target addresses using an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible JSON-RPC `request` function +Detect proxy contracts and their target addresses using an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible JSON-RPC `request` function. zero dependencies This package offers a utility function for checking if a smart contract at a given address implements one of the known proxy patterns. It detects the following kinds of proxies: @@ -10,7 +10,7 @@ It detects the following kinds of proxies: - [EIP-897](https://eips.ethereum.org/EIPS/eip-897) Delegate Proxy Pattern - [EIP-1822](https://eips.ethereum.org/EIPS/eip-1822) Universal Upgradeable Proxy Standard - OpenZeppelin Proxy Pattern -- Gnosis Safe Proxy Contract +- Safe Proxy Contract ## Installation @@ -36,12 +36,12 @@ The promise resolves to `null` if no proxy can be detected. ```ts import { InfuraProvider } from '@ethersproject/providers' -import detectProxyTarget from 'evm-proxy-detection' +import detectProxy from 'evm-proxy-detection' const infuraProvider = new InfuraProvider(1, process.env.INFURA_API_KEY) const requestFunc = ({ method, params }) => infuraProvider.send(method, params) -const target = await detectProxyTarget( +const target = await detectProxy( '0xA7AeFeaD2F25972D80516628417ac46b3F2604Af', requestFunc ) @@ -55,11 +55,11 @@ Otherwise, you can use providers like [eip1193-provider](https://www.npmjs.com/p ```ts import Web3 from 'web3' -import detectProxyTarget from 'evm-proxy-detection' +import detectProxy from 'evm-proxy-detection' const web3 = new Web3(Web3.givenProvider || 'ws://localhost:8545') -const target = await detectProxyTarget( +const target = await detectProxy( '0xA7AeFeaD2F25972D80516628417ac46b3F2604Af', web3.currentProvider.request ) @@ -69,13 +69,40 @@ console.log(target) // logs "0x4bd844F72A8edD323056130A86FC624D0dbcF5b0" ## API ```ts -detectProxyTarget(address: string, jsonRpcRequest: EIP1193ProviderRequestFunc, blockTag?: BlockTag): Promise +detectProxy(address: `0x${string}`, jsonRpcRequest: EIP1193ProviderRequestFunc, blockTag?: BlockTag): Promise ``` **Arguments** -- `address` (string): The address of the proxy contract -- `jsonRpcRequest` (EIP1193ProviderRequestFunc): A JSON-RPC request function, compatible with [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) (`(method: string, params: any[]) => Promise`) +- `address`: The address of the proxy contract +- `jsonRpcRequest`: A JSON-RPC request function, compatible with [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) (`(method: string, params: any[]) => Promise`) - `blockTag` (optional: BlockTag): `"earliest"`, `"latest"`, `"pending"` or hex block number, default is `"latest"` -The function returns a promise that will generally resolve to either the detected target contract address (non-checksummed) or `null` if it couldn't detect one. +**Return value** + +The function returns a promise that will generally resolve to either a `Result` object describing the detected proxy or `null` if it couldn't detect one. + +```ts +interface Result { + address: `0x${string}` + immutable: boolean + type: ProxyType +} +``` + +- `address`: The address (non-checksummed) of the proxy target +- `immutable`: Indicates if the proxy is immutable, meaning that the target address will never change +- `type`: Identifies the detected proxy type (possible values shown below) + +```ts +enum ProxyType { + Eip1167 = 'Eip1167', + Eip1967Direct = 'Eip1967Direct', + Eip1967Beacon = 'Eip1967Beacon', + Eip1822 = 'Eip1822', + Eip897 = 'Eip897', + OpenZeppelin = 'OpenZeppelin', + Safe = 'Safe', + Comptroller = 'Comptroller', +} +``` diff --git a/package.json b/package.json index fb8c152..fd1a797 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evm-proxy-detection", - "version": "1.2.0", + "version": "2.0.0", "description": "Detect proxy contracts and their target addresses using en EIP-1193 compatible JSON-RPC request function", "repository": "git@github.com:gnosis/ethers-proxies.git", "author": "Jan-Felix ", @@ -47,4 +47,4 @@ "typescript": "^4.7.4" }, "packageManager": "yarn@3.5.0" -} +} \ No newline at end of file diff --git a/src/eip1167.ts b/src/eip1167.ts new file mode 100644 index 0000000..4e022ab --- /dev/null +++ b/src/eip1167.ts @@ -0,0 +1,45 @@ +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')}` +} diff --git a/src/index.ts b/src/index.ts index d762e80..1d137ec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,15 @@ -import { BlockTag, EIP1193ProviderRequestFunc } from './types' +import { parse1167Bytecode } from './eip1167' +import { + BlockTag, + EIP1193ProviderRequestFunc, + ProxyType, + Result, +} from './types' // obtained as bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) const EIP_1967_LOGIC_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' -// obtained as bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) -const EIP_1967_BEACON_SLOT = - '0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50' - // obtained as keccak256("org.zeppelinos.proxy.implementation") const OPEN_ZEPPELIN_IMPLEMENTATION_SLOT = '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3' @@ -16,20 +18,27 @@ const OPEN_ZEPPELIN_IMPLEMENTATION_SLOT = const EIP_1822_LOGIC_SLOT = '0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7' -const EIP_1167_BEACON_METHODS = [ +// obtained as bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) +const EIP_1967_BEACON_SLOT = + '0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50' + +const EIP_897_INTERFACE = [ // bytes4(keccak256("implementation()")) padded to 32 bytes '0x5c60da1b00000000000000000000000000000000000000000000000000000000', - // bytes4(keccak256("childImplementation()")) padded to 32 bytes - // some implementations use this over the standard method name so that the beacon contract is not detected as an EIP-897 proxy itself - '0xda52571600000000000000000000000000000000000000000000000000000000', + + // bytes4(keccak256("proxyType()")) padded to 32 bytes + '0x4555d5c900000000000000000000000000000000000000000000000000000000', ] -const EIP_897_INTERFACE = [ +const EIP_1967_BEACON_METHODS = [ // bytes4(keccak256("implementation()")) padded to 32 bytes '0x5c60da1b00000000000000000000000000000000000000000000000000000000', + // bytes4(keccak256("childImplementation()")) padded to 32 bytes + // some implementations use this over the standard method name so that the beacon contract is not detected as an EIP-897 proxy itself + '0xda52571600000000000000000000000000000000000000000000000000000000', ] -const GNOSIS_SAFE_PROXY_INTERFACE = [ +const SAFE_PROXY_INTERFACE = [ // bytes4(keccak256("masterCopy()")) padded to 32 bytes '0xa619486e00000000000000000000000000000000000000000000000000000000', ] @@ -39,11 +48,11 @@ const COMPTROLLER_PROXY_INTERFACE = [ '0xbb82aa5e00000000000000000000000000000000000000000000000000000000', ] -const detectProxyTarget = ( - proxyAddress: string, +const detectProxy = ( + proxyAddress: `0x${string}`, jsonRpcRequest: EIP1193ProviderRequestFunc, blockTag: BlockTag = 'latest' -): Promise => +): Promise => Promise.any([ // EIP-1167 Minimal Proxy Contract jsonRpcRequest({ @@ -51,13 +60,24 @@ const detectProxyTarget = ( params: [proxyAddress, blockTag], }) .then(parse1167Bytecode) - .then(readAddress), + .then(readAddress) + .then((address) => ({ + address, + type: ProxyType.Eip1167, + immutable: true, + })), // EIP-1967 direct proxy jsonRpcRequest({ method: 'eth_getStorageAt', params: [proxyAddress, EIP_1967_LOGIC_SLOT, blockTag], - }).then(readAddress), + }) + .then(readAddress) + .then((address) => ({ + address, + type: ProxyType.Eip1967Direct, + immutable: false, + })), // EIP-1967 beacon proxy jsonRpcRequest({ @@ -71,7 +91,7 @@ const detectProxyTarget = ( params: [ { to: beaconAddress, - data: EIP_1167_BEACON_METHODS[0], + data: EIP_1967_BEACON_METHODS[0], }, blockTag, ], @@ -81,26 +101,43 @@ const detectProxyTarget = ( params: [ { to: beaconAddress, - data: EIP_1167_BEACON_METHODS[1], + data: EIP_1967_BEACON_METHODS[1], }, blockTag, ], }) ) ) - .then(readAddress), + .then(readAddress) + .then((address) => ({ + address, + type: ProxyType.Eip1967Beacon, + immutable: false, + })), // OpenZeppelin proxy pattern jsonRpcRequest({ method: 'eth_getStorageAt', params: [proxyAddress, OPEN_ZEPPELIN_IMPLEMENTATION_SLOT, blockTag], - }).then(readAddress), + }) + .then(readAddress) + .then((address) => ({ + address, + type: ProxyType.OpenZeppelin, + immutable: false, + })), // EIP-1822 Universal Upgradeable Proxy Standard jsonRpcRequest({ method: 'eth_getStorageAt', params: [proxyAddress, EIP_1822_LOGIC_SLOT, blockTag], - }).then(readAddress), + }) + .then(readAddress) + .then((address) => ({ + address, + type: ProxyType.Eip1822, + immutable: false, + })), // EIP-897 DelegateProxy pattern jsonRpcRequest({ @@ -112,19 +149,43 @@ const detectProxyTarget = ( }, blockTag, ], - }).then(readAddress), + }) + .then(readAddress) + .then(async (address) => ({ + address, + type: ProxyType.Eip897, + // proxyType === 1 means that the proxy is immutable + immutable: + (await jsonRpcRequest({ + method: 'eth_call', + params: [ + { + to: proxyAddress, + data: EIP_897_INTERFACE[1], + }, + blockTag, + ], + })) === + '0x0000000000000000000000000000000000000000000000000000000000000001', + })), - // GnosisSafeProxy contract + // SafeProxy contract jsonRpcRequest({ method: 'eth_call', params: [ { to: proxyAddress, - data: GNOSIS_SAFE_PROXY_INTERFACE[0], + data: SAFE_PROXY_INTERFACE[0], }, blockTag, ], - }).then(readAddress), + }) + .then(readAddress) + .then((address) => ({ + address, + type: ProxyType.Safe, + immutable: false, + })), // Comptroller proxy jsonRpcRequest({ @@ -136,10 +197,17 @@ const detectProxyTarget = ( }, blockTag, ], - }).then(readAddress), + }) + .then(readAddress) + .then((address) => ({ + address, + type: ProxyType.Comptroller, + immutable: false, + })), ]).catch(() => null) -const readAddress = (value: unknown): string => { +const zeroAddress = '0x' + '0'.repeat(40) +const readAddress = (value: unknown) => { if (typeof value !== 'string' || value === '0x') { throw new Error(`Invalid address value: ${value}`) } @@ -149,58 +217,11 @@ const readAddress = (value: unknown): string => { address = '0x' + address.slice(-40) } - 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')}` + return address as `0x${string}` } -export default detectProxyTarget +export default detectProxy diff --git a/src/types.ts b/src/types.ts index 9ccdd29..d2794cf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,27 @@ -export type BlockTag = number | 'earliest' | 'latest' | 'pending'; +export enum ProxyType { + Eip1167 = 'Eip1167', + Eip1967Direct = 'Eip1967Direct', + Eip1967Beacon = 'Eip1967Beacon', + Eip1822 = 'Eip1822', + Eip897 = 'Eip897', + OpenZeppelin = 'OpenZeppelin', + Safe = 'Safe', + Comptroller = 'Comptroller', +} -export interface RequestArguments { - method: string; - params: unknown[]; +export interface Result { + address: `0x${string}` + type: ProxyType + immutable: boolean } -export type EIP1193ProviderRequestFunc = (args: RequestArguments) => Promise +export type BlockTag = number | 'earliest' | 'latest' | 'pending' + +export interface RequestArguments { + method: string + params: unknown[] +} +export type EIP1193ProviderRequestFunc = ( + args: RequestArguments +) => Promise diff --git a/test/detectProxyTarget.spec.ts b/test/detectProxy.spec.ts similarity index 87% rename from test/detectProxyTarget.spec.ts rename to test/detectProxy.spec.ts index f0b41f3..ed8ca1d 100644 --- a/test/detectProxyTarget.spec.ts +++ b/test/detectProxy.spec.ts @@ -1,8 +1,8 @@ import { InfuraProvider } from '@ethersproject/providers' import { EIP1193ProviderRequestFunc } from '../src/types' -import detectProxyTarget from '../src' +import detectProxy from '../src' -describe('detectProxyTarget -> eip1193 provider', () => { +describe('detectProxy -> eip1193 provider', () => { const infuraProvider = new InfuraProvider(1, process.env.INFURA_API_KEY) const requestFunc: EIP1193ProviderRequestFunc = ({ method, params }) => infuraProvider.send(method, params) @@ -12,7 +12,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects EIP-1967 direct proxies', async () => { expect( - await detectProxyTarget( + await detectProxy( '0xA7AeFeaD2F25972D80516628417ac46b3F2604Af', requestFunc, BLOCK_TAG @@ -22,7 +22,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects EIP-1967 beacon proxies', async () => { expect( - await detectProxyTarget( + await detectProxy( '0xDd4e2eb37268B047f55fC5cAf22837F9EC08A881', requestFunc, BLOCK_TAG @@ -32,7 +32,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects EIP-1967 beacon variant proxies', async () => { expect( - await detectProxyTarget( + await detectProxy( '0x114f1388fAB456c4bA31B1850b244Eedcd024136', requestFunc, BLOCK_TAG @@ -42,7 +42,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects OpenZeppelin proxies', async () => { expect( - await detectProxyTarget( + await detectProxy( '0xed7e6720Ac8525Ac1AEee710f08789D02cD87ecB', requestFunc, BLOCK_TAG @@ -52,7 +52,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects EIP-897 delegate proxies', async () => { expect( - await detectProxyTarget( + await detectProxy( '0x8260b9eC6d472a34AD081297794d7Cc00181360a', requestFunc, BLOCK_TAG @@ -62,7 +62,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects EIP-1167 minimal proxies', async () => { expect( - await detectProxyTarget( + await detectProxy( '0x6d5d9b6ec51c15f45bfa4c460502403351d5b999', requestFunc, BLOCK_TAG @@ -72,7 +72,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects EIP-1167 minimal proxies with vanity addresses', async () => { expect( - await detectProxyTarget( + await detectProxy( '0xa81043fd06D57D140f6ad8C2913DbE87fdecDd5F', requestFunc, BLOCK_TAG @@ -82,7 +82,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('detects Gnosis Safe proxies', async () => { expect( - await detectProxyTarget( + await detectProxy( '0x0DA0C3e52C977Ed3cBc641fF02DD271c3ED55aFe', requestFunc, BLOCK_TAG @@ -92,7 +92,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it("detects Compound's custom proxy", async () => { expect( - await detectProxyTarget( + await detectProxy( '0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B', requestFunc, BLOCK_TAG @@ -102,7 +102,7 @@ describe('detectProxyTarget -> eip1193 provider', () => { it('resolves to null if no proxy target is detected', async () => { expect( - await detectProxyTarget( + await detectProxy( '0x5864c777697Bf9881220328BF2f16908c9aFCD7e', requestFunc, BLOCK_TAG From 5738aa2d93cc45d2ca01d290bfcabab3de9fc05a Mon Sep 17 00:00:00 2001 From: Jan-Felix Date: Fri, 26 Apr 2024 21:48:26 +0200 Subject: [PATCH 2/5] docs updates --- .cspell.json | 1 + README.md | 32 ++++++++++++++++++++++++-------- package.json | 6 +++--- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/.cspell.json b/.cspell.json index 3dc49da..38ecbbe 100644 --- a/.cspell.json +++ b/.cspell.json @@ -8,6 +8,7 @@ "ethersproject", "keccak", "PROXIABLE", + "viem", "zeppelinos" ], "flagWords": [], diff --git a/README.md b/README.md index f3b00ae..e38381b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # evm-proxy-detection -Detect proxy contracts and their target addresses using an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible JSON-RPC `request` function. zero dependencies +A zero dependencies module to detect proxy contracts and their target addresses using an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible JSON-RPC `request` function. -This package offers a utility function for checking if a smart contract at a given address implements one of the known proxy patterns. It detects the following kinds of proxies: - [EIP-1167](https://eips.ethereum.org/EIPS/eip-1167) Minimal Proxy Contract @@ -29,9 +28,24 @@ yarn add evm-proxy-detection ## How to use The function requires an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible `request` function that it uses to make JSON-RPC requests to run a set of checks against the given address. -It returns a promise that resolves to the proxy target address, i.e., the address of the contract implementing the logic. +It returns a promise that resolves to result object with the proxy target address, i.e., the address of the contract implementing the logic, and information about the detected proxy type. The promise resolves to `null` if no proxy can be detected. +### Viem + +```ts +import { createPublicClient, http } from 'viem' + +const client = createPublicClient({ + chain, + // enable json-rpc batching to reduce the number of http requests + transport: http(undefined, { batch: true }), +}) + +const result = await detectProxy(address, client.request) +// logs: { target: "0x4bd844F72A8edD323056130A86FC624D0dbcF5b0", type: 'EIP-1967', immutable: false } +``` + ### Ethers with an adapter function ```ts @@ -45,7 +59,8 @@ const target = await detectProxy( '0xA7AeFeaD2F25972D80516628417ac46b3F2604Af', requestFunc ) -console.log(target) // logs "0x4bd844F72A8edD323056130A86FC624D0dbcF5b0" +console.log(target) +// logs: { target: "0x4bd844F72A8edD323056130A86FC624D0dbcF5b0", type: 'EIP-1967', immutable: false } ``` ### Web3 with an EIP1193 provider @@ -59,11 +74,12 @@ import detectProxy from 'evm-proxy-detection' const web3 = new Web3(Web3.givenProvider || 'ws://localhost:8545') -const target = await detectProxy( +const result = await detectProxy( '0xA7AeFeaD2F25972D80516628417ac46b3F2604Af', web3.currentProvider.request ) -console.log(target) // logs "0x4bd844F72A8edD323056130A86FC624D0dbcF5b0" +console.log(result) +// logs: { target: "0x4bd844F72A8edD323056130A86FC624D0dbcF5b0", type: 'EIP-1967', immutable: false } ``` ## API @@ -84,13 +100,13 @@ The function returns a promise that will generally resolve to either a `Result` ```ts interface Result { - address: `0x${string}` + target: `0x${string}` immutable: boolean type: ProxyType } ``` -- `address`: The address (non-checksummed) of the proxy target +- `target`: The address (non-checksummed) of the proxy target - `immutable`: Indicates if the proxy is immutable, meaning that the target address will never change - `type`: Identifies the detected proxy type (possible values shown below) diff --git a/package.json b/package.json index fd1a797..dc1d384 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "evm-proxy-detection", "version": "2.0.0", - "description": "Detect proxy contracts and their target addresses using en EIP-1193 compatible JSON-RPC request function", - "repository": "git@github.com:gnosis/ethers-proxies.git", - "author": "Jan-Felix ", + "description": "Detect proxy contracts and their target addresses using an EIP-1193 compatible JSON-RPC request function", + "repository": "https://github.com/abipub/evm-proxy-detection.git", + "author": "Jan-Felix ", "license": "MIT", "main": "build/cjs/index.js", "typings": "build/cjs/index.d.ts", From bca83b606cc161763f84388fb6f52b5f180ee956 Mon Sep 17 00:00:00 2001 From: Jan-Felix Date: Fri, 26 Apr 2024 22:01:47 +0200 Subject: [PATCH 3/5] fix project tooling --- .vscode/extensions.json | 7 + .vscode/settings.json | 10 + .yarn/sdks/eslint/bin/eslint.js | 20 ++ .yarn/sdks/eslint/lib/api.js | 20 ++ .yarn/sdks/eslint/lib/unsupported-api.js | 20 ++ .yarn/sdks/eslint/package.json | 14 ++ .yarn/sdks/integrations.yml | 5 + .yarn/sdks/prettier/bin-prettier.js | 20 ++ .yarn/sdks/prettier/index.js | 20 ++ .yarn/sdks/prettier/package.json | 7 + .yarn/sdks/typescript/bin/tsc | 20 ++ .yarn/sdks/typescript/bin/tsserver | 20 ++ .yarn/sdks/typescript/lib/tsc.js | 20 ++ .yarn/sdks/typescript/lib/tsserver.js | 225 +++++++++++++++++++ .yarn/sdks/typescript/lib/tsserverlibrary.js | 225 +++++++++++++++++++ .yarn/sdks/typescript/lib/typescript.js | 20 ++ .yarn/sdks/typescript/package.json | 10 + package.json | 4 +- src/index.ts | 2 + tsconfig.json | 6 +- yarn.lock | 150 ++++++++++++- 21 files changed, 830 insertions(+), 15 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100755 .yarn/sdks/eslint/bin/eslint.js create mode 100644 .yarn/sdks/eslint/lib/api.js create mode 100644 .yarn/sdks/eslint/lib/unsupported-api.js create mode 100644 .yarn/sdks/eslint/package.json create mode 100644 .yarn/sdks/integrations.yml create mode 100755 .yarn/sdks/prettier/bin-prettier.js create mode 100644 .yarn/sdks/prettier/index.js create mode 100644 .yarn/sdks/prettier/package.json create mode 100755 .yarn/sdks/typescript/bin/tsc create mode 100755 .yarn/sdks/typescript/bin/tsserver create mode 100644 .yarn/sdks/typescript/lib/tsc.js create mode 100644 .yarn/sdks/typescript/lib/tsserver.js create mode 100644 .yarn/sdks/typescript/lib/tsserverlibrary.js create mode 100644 .yarn/sdks/typescript/lib/typescript.js create mode 100644 .yarn/sdks/typescript/package.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..daaa5ee --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "arcanis.vscode-zipfs", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6278784 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "search.exclude": { + "**/.yarn": true, + "**/.pnp.*": true + }, + "eslint.nodePath": ".yarn/sdks", + "prettier.prettierPath": ".yarn/sdks/prettier/index.js", + "typescript.tsdk": ".yarn/sdks/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/.yarn/sdks/eslint/bin/eslint.js b/.yarn/sdks/eslint/bin/eslint.js new file mode 100755 index 0000000..9ef98e4 --- /dev/null +++ b/.yarn/sdks/eslint/bin/eslint.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/bin/eslint.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real eslint/bin/eslint.js your application uses +module.exports = absRequire(`eslint/bin/eslint.js`); diff --git a/.yarn/sdks/eslint/lib/api.js b/.yarn/sdks/eslint/lib/api.js new file mode 100644 index 0000000..653b22b --- /dev/null +++ b/.yarn/sdks/eslint/lib/api.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint + require(absPnpApiPath).setup(); + } +} + +// Defer to the real eslint your application uses +module.exports = absRequire(`eslint`); diff --git a/.yarn/sdks/eslint/lib/unsupported-api.js b/.yarn/sdks/eslint/lib/unsupported-api.js new file mode 100644 index 0000000..30fdf15 --- /dev/null +++ b/.yarn/sdks/eslint/lib/unsupported-api.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require eslint/use-at-your-own-risk + require(absPnpApiPath).setup(); + } +} + +// Defer to the real eslint/use-at-your-own-risk your application uses +module.exports = absRequire(`eslint/use-at-your-own-risk`); diff --git a/.yarn/sdks/eslint/package.json b/.yarn/sdks/eslint/package.json new file mode 100644 index 0000000..1eab10f --- /dev/null +++ b/.yarn/sdks/eslint/package.json @@ -0,0 +1,14 @@ +{ + "name": "eslint", + "version": "8.19.0-sdk", + "main": "./lib/api.js", + "type": "commonjs", + "bin": { + "eslint": "./bin/eslint.js" + }, + "exports": { + "./package.json": "./package.json", + ".": "./lib/api.js", + "./use-at-your-own-risk": "./lib/unsupported-api.js" + } +} diff --git a/.yarn/sdks/integrations.yml b/.yarn/sdks/integrations.yml new file mode 100644 index 0000000..aa9d0d0 --- /dev/null +++ b/.yarn/sdks/integrations.yml @@ -0,0 +1,5 @@ +# This file is automatically generated by @yarnpkg/sdks. +# Manual changes might be lost! + +integrations: + - vscode diff --git a/.yarn/sdks/prettier/bin-prettier.js b/.yarn/sdks/prettier/bin-prettier.js new file mode 100755 index 0000000..73f04ca --- /dev/null +++ b/.yarn/sdks/prettier/bin-prettier.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier/bin-prettier.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier/bin-prettier.js your application uses +module.exports = absRequire(`prettier/bin-prettier.js`); diff --git a/.yarn/sdks/prettier/index.js b/.yarn/sdks/prettier/index.js new file mode 100644 index 0000000..8758e36 --- /dev/null +++ b/.yarn/sdks/prettier/index.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier your application uses +module.exports = absRequire(`prettier`); diff --git a/.yarn/sdks/prettier/package.json b/.yarn/sdks/prettier/package.json new file mode 100644 index 0000000..30504bf --- /dev/null +++ b/.yarn/sdks/prettier/package.json @@ -0,0 +1,7 @@ +{ + "name": "prettier", + "version": "2.7.1-sdk", + "main": "./index.js", + "type": "commonjs", + "bin": "./bin-prettier.js" +} diff --git a/.yarn/sdks/typescript/bin/tsc b/.yarn/sdks/typescript/bin/tsc new file mode 100755 index 0000000..454b950 --- /dev/null +++ b/.yarn/sdks/typescript/bin/tsc @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsc + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsc your application uses +module.exports = absRequire(`typescript/bin/tsc`); diff --git a/.yarn/sdks/typescript/bin/tsserver b/.yarn/sdks/typescript/bin/tsserver new file mode 100755 index 0000000..d7a6056 --- /dev/null +++ b/.yarn/sdks/typescript/bin/tsserver @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/bin/tsserver + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/bin/tsserver your application uses +module.exports = absRequire(`typescript/bin/tsserver`); diff --git a/.yarn/sdks/typescript/lib/tsc.js b/.yarn/sdks/typescript/lib/tsc.js new file mode 100644 index 0000000..2f62fc9 --- /dev/null +++ b/.yarn/sdks/typescript/lib/tsc.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsc.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsc.js your application uses +module.exports = absRequire(`typescript/lib/tsc.js`); diff --git a/.yarn/sdks/typescript/lib/tsserver.js b/.yarn/sdks/typescript/lib/tsserver.js new file mode 100644 index 0000000..bbb1e46 --- /dev/null +++ b/.yarn/sdks/typescript/lib/tsserver.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserver.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserver.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); diff --git a/.yarn/sdks/typescript/lib/tsserverlibrary.js b/.yarn/sdks/typescript/lib/tsserverlibrary.js new file mode 100644 index 0000000..a68f028 --- /dev/null +++ b/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + // 2021-10-08: VSCode changed the format in 1.61. + // Before | ^zip:/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + // 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // + // 2022-05-06: VSCode changed the format in 1.68 + // Before | ^/zip/c:/foo/bar.zip/package.json + // After | ^/zip//c:/foo/bar.zip/package.json + // + case `vscode <1.61`: { + str = `^zip:${str}`; + } break; + + case `vscode <1.66`: { + str = `^/zip/${str}`; + } break; + + case `vscode <1.68`: { + str = `^/zip${str}`; + } break; + + case `vscode`: { + str = `^/zip/${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); + } + } + + return str; + } + + function fromEditorPath(str) { + switch (hostInfo) { + case `coc-nvim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } break; + + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + + case `vscode`: + default: { + return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) + } break; + } + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? []).map(Number) + + if (major === 1) { + if (minor < 61) { + hostInfo += ` <1.61`; + } else if (minor < 66) { + hostInfo += ` <1.66`; + } else if (minor < 68) { + hostInfo += ` <1.68`; + } + } + } + } + + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserverlibrary.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserverlibrary.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); diff --git a/.yarn/sdks/typescript/lib/typescript.js b/.yarn/sdks/typescript/lib/typescript.js new file mode 100644 index 0000000..b5f4db2 --- /dev/null +++ b/.yarn/sdks/typescript/lib/typescript.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript your application uses +module.exports = absRequire(`typescript`); diff --git a/.yarn/sdks/typescript/package.json b/.yarn/sdks/typescript/package.json new file mode 100644 index 0000000..4a0495f --- /dev/null +++ b/.yarn/sdks/typescript/package.json @@ -0,0 +1,10 @@ +{ + "name": "typescript", + "version": "4.7.4-sdk", + "main": "./lib/typescript.js", + "type": "commonjs", + "bin": { + "tsc": "./bin/tsc", + "tsserver": "./bin/tsserver" + } +} diff --git a/package.json b/package.json index dc1d384..7776f85 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@ethersproject/providers": "^5.6.8", - "@types/jest": "^28.1.5", + "@types/jest": "^29.5.12", "@types/node": "^18.0.4", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", @@ -47,4 +47,4 @@ "typescript": "^4.7.4" }, "packageManager": "yarn@3.5.0" -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 1d137ec..61ac094 100644 --- a/src/index.ts +++ b/src/index.ts @@ -225,3 +225,5 @@ const readAddress = (value: unknown) => { } export default detectProxy + +export { parse1167Bytecode } diff --git a/tsconfig.json b/tsconfig.json index 1c9b0cd..0dce0d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,10 +32,8 @@ "pretty": true /* Stylize errors and messages using color and context. */, "lib": ["es2021"], - "types": ["jest"], - "typeRoots": ["node_modules/@types"] + "types": ["jest"] }, - "files": ["src/index.ts"], - "include": ["src/**/*.ts", "test/**/*.ts", "hardhat.config.ts", "typechain"], + "include": ["src/**/*.ts", "test/**/*.ts"], "compileOnSave": false } diff --git a/yarn.lock b/yarn.lock index 51d1ba3..44f1cc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1166,6 +1166,15 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: ^29.6.3 + checksum: 75eb177f3d00b6331bcaa057e07c0ccb0733a1d0a1943e1d8db346779039cb7f103789f16e502f888a3096fb58c2300c38d1f3748b36a7fa762eb6f6d1b160ed + languageName: node + linkType: hard + "@jest/expect@npm:^28.1.3": version: 28.1.3 resolution: "@jest/expect@npm:28.1.3" @@ -1248,6 +1257,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": ^0.27.8 + checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93 + languageName: node + linkType: hard + "@jest/source-map@npm:^28.1.2": version: 28.1.2 resolution: "@jest/source-map@npm:28.1.2" @@ -1320,6 +1338,20 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": ^29.6.3 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: a0bcf15dbb0eca6bdd8ce61a3fb055349d40268622a7670a3b2eb3c3dbafe9eb26af59938366d520b86907b9505b0f9b29b85cec11579a9e580694b87cd90fcc + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.1.0": version: 0.1.1 resolution: "@jridgewell/gen-mapping@npm:0.1.1" @@ -1436,6 +1468,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1 + languageName: node + linkType: hard + "@sinonjs/commons@npm:^1.7.0": version: 1.8.3 resolution: "@sinonjs/commons@npm:1.8.3" @@ -1564,13 +1603,13 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:^28.1.5": - version: 28.1.5 - resolution: "@types/jest@npm:28.1.5" +"@types/jest@npm:^29.5.12": + version: 29.5.12 + resolution: "@types/jest@npm:29.5.12" dependencies: - jest-matcher-utils: ^28.0.0 - pretty-format: ^28.0.0 - checksum: 994bfc25a5e767ec1506a2a7d94e60a7a5645b2e4e8444c56ae902f3a3e27ae54e821e41c1b4e7c8d7f5022bf5abfdd0460ead223ba6f2d0a194e5b2bed1ad76 + expect: ^29.0.0 + pretty-format: ^29.0.0 + checksum: 19b1efdeed9d9a60a81edc8226cdeae5af7479e493eaed273e01243891c9651f7b8b4c08fc633a7d0d1d379b091c4179bbaa0807af62542325fd72f2dd17ce1c languageName: node linkType: hard @@ -2672,6 +2711,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: f4914158e1f2276343d98ff5b31fc004e7304f5470bf0f1adb2ac6955d85a531a6458d33e87667f98f6ae52ebd3891bb47d420bb48a5bd8b7a27ee25b20e33aa + languageName: node + linkType: hard + "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -3095,7 +3141,7 @@ __metadata: resolution: "evm-proxy-detection@workspace:." dependencies: "@ethersproject/providers": ^5.6.8 - "@types/jest": ^28.1.5 + "@types/jest": ^29.5.12 "@types/node": ^18.0.4 "@typescript-eslint/eslint-plugin": ^5.30.6 "@typescript-eslint/parser": ^5.30.6 @@ -3151,6 +3197,19 @@ __metadata: languageName: node linkType: hard +"expect@npm:^29.0.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": ^29.7.0 + jest-get-type: ^29.6.3 + jest-matcher-utils: ^29.7.0 + jest-message-util: ^29.7.0 + jest-util: ^29.7.0 + checksum: 9257f10288e149b81254a0fda8ffe8d54a7061cd61d7515779998b012579d2b8c22354b0eb901daf0145f347403da582f75f359f4810c007182ad3fb318b5c0c + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -4147,6 +4206,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^29.6.3 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: 08e24a9dd43bfba1ef07a6374e5af138f53137b79ec3d5cc71a2303515335898888fa5409959172e1e05de966c9e714368d15e8994b0af7441f0721ee8e1bb77 + languageName: node + linkType: hard + "jest-docblock@npm:^28.1.1": version: 28.1.1 resolution: "jest-docblock@npm:28.1.1" @@ -4190,6 +4261,13 @@ __metadata: languageName: node linkType: hard +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 88ac9102d4679d768accae29f1e75f592b760b44277df288ad76ce5bf038c3f5ce3719dea8aa0f035dac30e9eb034b848ce716b9183ad7cc222d029f03e92205 + languageName: node + linkType: hard + "jest-haste-map@npm:^28.1.3": version: 28.1.3 resolution: "jest-haste-map@npm:28.1.3" @@ -4223,7 +4301,7 @@ __metadata: languageName: node linkType: hard -"jest-matcher-utils@npm:^28.0.0, jest-matcher-utils@npm:^28.1.3": +"jest-matcher-utils@npm:^28.1.3": version: 28.1.3 resolution: "jest-matcher-utils@npm:28.1.3" dependencies: @@ -4235,6 +4313,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: ^4.0.0 + jest-diff: ^29.7.0 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: d7259e5f995d915e8a37a8fd494cb7d6af24cd2a287b200f831717ba0d015190375f9f5dc35393b8ba2aae9b2ebd60984635269c7f8cff7d85b077543b7744cd + languageName: node + linkType: hard + "jest-message-util@npm:^28.1.3": version: 28.1.3 resolution: "jest-message-util@npm:28.1.3" @@ -4252,6 +4342,23 @@ __metadata: languageName: node linkType: hard +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": ^7.12.13 + "@jest/types": ^29.6.3 + "@types/stack-utils": ^2.0.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + micromatch: ^4.0.4 + pretty-format: ^29.7.0 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: a9d025b1c6726a2ff17d54cc694de088b0489456c69106be6b615db7a51b7beb66788bea7a59991a019d924fbf20f67d085a445aedb9a4d6760363f4d7d09930 + languageName: node + linkType: hard + "jest-mock@npm:^28.1.3": version: 28.1.3 resolution: "jest-mock@npm:28.1.3" @@ -4412,6 +4519,20 @@ __metadata: languageName: node linkType: hard +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": ^29.6.3 + "@types/node": "*" + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: 042ab4980f4ccd4d50226e01e5c7376a8556b472442ca6091a8f102488c0f22e6e8b89ea874111d2328a2080083bf3225c86f3788c52af0bd0345a00eb57a3ca + languageName: node + linkType: hard + "jest-validate@npm:^28.1.3": version: 28.1.3 resolution: "jest-validate@npm:28.1.3" @@ -5268,7 +5389,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^28.0.0, pretty-format@npm:^28.1.3": +"pretty-format@npm:^28.1.3": version: 28.1.3 resolution: "pretty-format@npm:28.1.3" dependencies: @@ -5280,6 +5401,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": ^29.6.3 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: 032c1602383e71e9c0c02a01bbd25d6759d60e9c7cf21937dde8357aa753da348fcec5def5d1002c9678a8524d5fe099ad98861286550ef44de8808cc61e43b6 + languageName: node + linkType: hard + "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" From 3e4c2696321938b5ee4029d0820ef4a039b25581 Mon Sep 17 00:00:00 2001 From: Jan-Felix Date: Fri, 26 Apr 2024 22:09:52 +0200 Subject: [PATCH 4/5] adjust tests --- src/eip1167.ts | 2 +- test/detectProxy.spec.ts | 62 +++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/eip1167.ts b/src/eip1167.ts index 4e022ab..7a27a4b 100644 --- a/src/eip1167.ts +++ b/src/eip1167.ts @@ -1,7 +1,7 @@ const EIP_1167_BYTECODE_PREFIX = '0x363d3d373d3d3d363d' const EIP_1167_BYTECODE_SUFFIX = '57fd5bf3' -export const parse1167Bytecode = (bytecode: unknown): string => { +export const parse1167Bytecode = (bytecode: unknown): `0x${string}` => { if ( typeof bytecode !== 'string' || !bytecode.startsWith(EIP_1167_BYTECODE_PREFIX) diff --git a/test/detectProxy.spec.ts b/test/detectProxy.spec.ts index ed8ca1d..ba3453e 100644 --- a/test/detectProxy.spec.ts +++ b/test/detectProxy.spec.ts @@ -2,13 +2,13 @@ import { InfuraProvider } from '@ethersproject/providers' import { EIP1193ProviderRequestFunc } from '../src/types' import detectProxy from '../src' -describe('detectProxy -> eip1193 provider', () => { +describe('detectProxy', () => { const infuraProvider = new InfuraProvider(1, process.env.INFURA_API_KEY) const requestFunc: EIP1193ProviderRequestFunc = ({ method, params }) => infuraProvider.send(method, params) // TODO fix to a block number to keep test stable for eternity (requires Infura archive access) - const BLOCK_TAG = 'latest' // 15573889 + const BLOCK_TAG = 'latest' // 19741734 it('detects EIP-1967 direct proxies', async () => { expect( @@ -17,7 +17,11 @@ describe('detectProxy -> eip1193 provider', () => { requestFunc, BLOCK_TAG ) - ).toBe('0x4bd844f72a8edd323056130a86fc624d0dbcf5b0') + ).toEqual({ + address: '0x4bd844f72a8edd323056130a86fc624d0dbcf5b0', + immutable: false, + type: 'Eip1967Direct', + }) }) it('detects EIP-1967 beacon proxies', async () => { @@ -27,7 +31,11 @@ describe('detectProxy -> eip1193 provider', () => { requestFunc, BLOCK_TAG ) - ).toBe('0xe5c048792dcf2e4a56000c8b6a47f21df22752d1') + ).toEqual({ + address: '0xe5c048792dcf2e4a56000c8b6a47f21df22752d1', + immutable: false, + type: 'Eip1967Beacon', + }) }) it('detects EIP-1967 beacon variant proxies', async () => { @@ -37,17 +45,25 @@ describe('detectProxy -> eip1193 provider', () => { requestFunc, BLOCK_TAG ) - ).toBe('0x36b799160cdc2d9809d108224d1967cc9b7d321c') + ).toEqual({ + address: '0x0fa0fd98727c443dd5275774c44d27cff9d279ed', + immutable: false, + type: 'Eip1967Beacon', + }) }) it('detects OpenZeppelin proxies', async () => { expect( await detectProxy( - '0xed7e6720Ac8525Ac1AEee710f08789D02cD87ecB', + '0xC986c2d326c84752aF4cC842E033B9ae5D54ebbB', requestFunc, BLOCK_TAG ) - ).toBe('0xe3f3c590e044969294b1730ad8647692faf0f604') + ).toEqual({ + address: '0x0656368c4934e56071056da375d4a691d22161f8', + immutable: false, + type: 'OpenZeppelin', + }) }) it('detects EIP-897 delegate proxies', async () => { @@ -57,7 +73,11 @@ describe('detectProxy -> eip1193 provider', () => { requestFunc, BLOCK_TAG ) - ).toBe('0xe4e4003afe3765aca8149a82fc064c0b125b9e5a') + ).toEqual({ + address: '0xe4e4003afe3765aca8149a82fc064c0b125b9e5a', + immutable: false, + type: 'Eip1967Direct', + }) }) it('detects EIP-1167 minimal proxies', async () => { @@ -67,7 +87,11 @@ describe('detectProxy -> eip1193 provider', () => { requestFunc, BLOCK_TAG ) - ).toBe('0x210ff9ced719e9bf2444dbc3670bac99342126fa') + ).toEqual({ + address: '0x210ff9ced719e9bf2444dbc3670bac99342126fa', + immutable: true, + type: 'Eip1167', + }) }) it('detects EIP-1167 minimal proxies with vanity addresses', async () => { @@ -77,17 +101,25 @@ describe('detectProxy -> eip1193 provider', () => { requestFunc, BLOCK_TAG ) - ).toBe('0x0000000010fd301be3200e67978e3cc67c962f48') + ).toEqual({ + address: '0x0000000010fd301be3200e67978e3cc67c962f48', + immutable: true, + type: 'Eip1167', + }) }) - it('detects Gnosis Safe proxies', async () => { + it('detects Safe proxies', async () => { expect( await detectProxy( '0x0DA0C3e52C977Ed3cBc641fF02DD271c3ED55aFe', requestFunc, BLOCK_TAG ) - ).toBe('0xd9db270c1b5e3bd161e8c8503c55ceabee709552') + ).toEqual({ + address: '0xd9db270c1b5e3bd161e8c8503c55ceabee709552', + immutable: false, + type: 'Safe', + }) }) it("detects Compound's custom proxy", async () => { @@ -97,7 +129,11 @@ describe('detectProxy -> eip1193 provider', () => { requestFunc, BLOCK_TAG ) - ).toBe('0xbafe01ff935c7305907c33bf824352ee5979b526') + ).toEqual({ + address: '0xbafe01ff935c7305907c33bf824352ee5979b526', + immutable: false, + type: 'Comptroller', + }) }) it('resolves to null if no proxy target is detected', async () => { From 05dfd5c8ba7f4bf39cddcf3761c0d983cb992c40 Mon Sep 17 00:00:00 2001 From: Jan-Felix Date: Fri, 26 Apr 2024 22:11:11 +0200 Subject: [PATCH 5/5] undo manual version bump --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7776f85..6c69474 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "evm-proxy-detection", - "version": "2.0.0", + "version": "1.2.0", "description": "Detect proxy contracts and their target addresses using an EIP-1193 compatible JSON-RPC request function", "repository": "https://github.com/abipub/evm-proxy-detection.git", "author": "Jan-Felix ", @@ -47,4 +47,4 @@ "typescript": "^4.7.4" }, "packageManager": "yarn@3.5.0" -} +} \ No newline at end of file