diff --git a/eslint.config.mjs b/eslint.config.mjs index d60a6e3ca..9b29627c6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,16 +1,13 @@ -import eslint from "@eslint/js"; -import tseslint from "typescript-eslint"; -import prettier from "eslint-plugin-prettier/recommended"; +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import prettier from 'eslint-plugin-prettier/recommended'; export default [ eslint.configs.recommended, ...tseslint.configs.recommended, { rules: { - "@typescript-eslint/no-unused-vars": [ - "error", - { ignoreRestSiblings: true }, - ], + '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }], }, }, prettier, diff --git a/src/__tests__/assets.test.ts b/src/__tests__/assets.test.ts index ebf1d2ed9..9ec6f85b9 100644 --- a/src/__tests__/assets.test.ts +++ b/src/__tests__/assets.test.ts @@ -1,8 +1,8 @@ import fs from 'fs'; import path from 'path'; -import { SingletonDeploymentJSON } from '../types'; +import { SingletonDeploymentJSON, AddressType } from '../types'; -const KNOWN_ADDRESS_TYPES = ['canonical', 'eip155', 'zksync']; +const KNOWN_ADDRESS_TYPES: AddressType[] = ['canonical', 'eip155', 'zksync']; function assetPath(...paths: string[]) { return path.join(__dirname, '..', 'assets', ...paths); @@ -45,7 +45,7 @@ describe('assets/', () => { expect(keys).toEqual(sorted); }); - it('networks should only contain canonical address types', async () => { + it('networks should only contain known address types', async () => { const deploymentJson = await readAssetJSON(version, file); if (!deploymentJson) { throw new Error(`Failed to read asset ${version}/${file}`); diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 00505115a..45d295399 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -7,283 +7,415 @@ import GnosisSafe120 from './assets/v1/v1.2.0/gnosis_safe.json'; import GnosisSafe111 from './assets/v1/v1.1.1/gnosis_safe.json'; import GnosisSafe100 from './assets/v1/v1.0.0/gnosis_safe.json'; import { findDeployment } from '../utils'; -import { _safeDeployments, _safeL2Deployments } from '../safes'; -import { SingletonDeployment, SingletonDeploymentJSON } from '../types'; +import { _SAFE_DEPLOYMENTS, _SAFE_L2_DEPLOYMENTS } from '../deployments'; +import { SingletonDeployment, SingletonDeploymentJSON, DeploymentFormats, SingletonDeploymentV2 } from '../types'; -const _safeDeploymentsReverse = [..._safeDeployments].reverse(); +const _safeDeploymentsReverse = [..._SAFE_DEPLOYMENTS].reverse(); describe('utils.ts', () => { describe('findDeployment', () => { - it('should filter by released by default', () => { - const testUnreleasedDeploymentJson: SingletonDeploymentJSON = { - version: '', - abi: [], - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + describe('singleton format', () => { + it('should filter by released by default', () => { + const testUnreleasedDeploymentJson: SingletonDeploymentJSON = { + version: '', + abi: [], + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': 'canonical' }, - contractName: '', - released: false, - }; - const testReleasedDeploymentJson: SingletonDeploymentJSON = { - version: '', - abi: [], - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + networkAddresses: { '1': 'canonical' }, + contractName: '', + released: false, + }; + const testReleasedDeploymentJson: SingletonDeploymentJSON = { + version: '', + abi: [], + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': 'canonical' }, - contractName: '', - released: true, // Default filter value - }; - const testReleasedDeployment: SingletonDeployment = { - defaultAddress: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + networkAddresses: { '1': 'canonical' }, + contractName: '', + released: true, // Default filter value + }; + const testReleasedDeployment: SingletonDeployment = { + defaultAddress: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef' }, - released: true, - abi: [], - version: '', - contractName: '', - }; + networkAddresses: { '1': '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef' }, + released: true, + abi: [], + version: '', + contractName: '', + }; - const testDeployments = [testUnreleasedDeploymentJson, testUnreleasedDeploymentJson, testReleasedDeploymentJson]; + const testDeployments = [ + testUnreleasedDeploymentJson, + testUnreleasedDeploymentJson, + testReleasedDeploymentJson, + ]; - expect(findDeployment(undefined, testDeployments)).toMatchObject(testReleasedDeployment); + expect(findDeployment(undefined, testDeployments)).toMatchObject(testReleasedDeployment); - // should preserve the released flag even if its not explicitly passed - expect(findDeployment({ network: '1' }, testDeployments)).toMatchObject(testReleasedDeployment); - }); + // should preserve the released flag even if its not explicitly passed + expect(findDeployment({ network: '1' }, testDeployments)).toMatchObject(testReleasedDeployment); + }); - it('should return the correct deployment (filtered by version)', () => { - // Chronological deployments - expect(findDeployment({ version: '1.3.0' }, _safeDeployments)).toMatchObject(GnosisSafe130); - expect(findDeployment({ version: '1.2.0' }, _safeDeployments)).toMatchObject(GnosisSafe120); - expect(findDeployment({ version: '1.1.1' }, _safeDeployments)).toMatchObject(GnosisSafe111); - expect(findDeployment({ version: '1.0.0' }, _safeDeployments)).toMatchObject(GnosisSafe100); - // Incorrect filter: - expect(findDeployment({ version: '2.0.0' }, _safeDeployments)).toBeUndefined(); + it('should return the correct deployment (filtered by version)', () => { + // Chronological deployments + expect(findDeployment({ version: '1.3.0' }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe130); + expect(findDeployment({ version: '1.2.0' }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe120); + expect(findDeployment({ version: '1.1.1' }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe111); + expect(findDeployment({ version: '1.0.0' }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe100); + // Incorrect filter: + expect(findDeployment({ version: '2.0.0' }, _SAFE_DEPLOYMENTS)).toBeUndefined(); - // L2 deployments - expect(findDeployment({ version: '1.3.0+L2' }, _safeL2Deployments)).toMatchObject(GnosisSafeL2130); - // Incorrect filter: - expect(findDeployment({ version: '2.0.0+L2' }, _safeL2Deployments)).toBeUndefined(); - }); + // L2 deployments + expect(findDeployment({ version: '1.3.0+L2' }, _SAFE_L2_DEPLOYMENTS)).toMatchObject(GnosisSafeL2130); + // Incorrect filter: + expect(findDeployment({ version: '2.0.0+L2' }, _SAFE_L2_DEPLOYMENTS)).toBeUndefined(); + }); - it('should return the correct deployment (filtered by released flag)', () => { - const testUnreleasedDeploymentJson: SingletonDeploymentJSON = { - version: '', - abi: [], - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + it('should return the correct deployment (filtered by released flag)', () => { + const testUnreleasedDeploymentJson: SingletonDeploymentJSON = { + version: '', + abi: [], + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': 'canonical' }, - contractName: '', - released: false, - }; - const testReleasedDeploymentJson: SingletonDeploymentJSON = { - version: '', - abi: [], - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + networkAddresses: { '1': 'canonical' }, + contractName: '', + released: false, + }; + const testReleasedDeploymentJson: SingletonDeploymentJSON = { + version: '', + abi: [], + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': 'canonical' }, - contractName: '', - released: true, // Default filter value - }; - const testUnreleasedDeployment: SingletonDeployment = { - defaultAddress: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + networkAddresses: { '1': 'canonical' }, + contractName: '', + released: true, // Default filter value + }; + const testUnreleasedDeployment: SingletonDeployment = { + defaultAddress: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef' }, - released: false, - abi: [], - version: '', - contractName: '', - }; + networkAddresses: { '1': '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef' }, + released: false, + abi: [], + version: '', + contractName: '', + }; - const testDeployments = [testUnreleasedDeploymentJson, testReleasedDeploymentJson]; + const testDeployments = [testUnreleasedDeploymentJson, testReleasedDeploymentJson]; - // Chronological deployments - expect(findDeployment({ released: true }, _safeDeployments)).toMatchObject(Safe141); + // Chronological deployments + expect(findDeployment({ released: true }, _SAFE_DEPLOYMENTS)).toMatchObject(Safe141); - // Reverse chronological deployments - expect(findDeployment({ released: true }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe100); - // Released flag set to false: - expect(findDeployment({ released: false }, testDeployments)).toMatchObject(testUnreleasedDeployment); + // Reverse chronological deployments + expect(findDeployment({ released: true }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe100); + // Released flag set to false: + expect(findDeployment({ released: false }, testDeployments)).toMatchObject(testUnreleasedDeployment); - // L2 deployments - expect(findDeployment({ released: true }, _safeL2Deployments)).toMatchObject(SafeL2141); - }); + // L2 deployments + expect(findDeployment({ released: true }, _SAFE_L2_DEPLOYMENTS)).toMatchObject(SafeL2141); + }); - it('should return the correct deployment (filtered by network)', () => { - // Reverse chronological deployments - expect(findDeployment({ network: '1' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe100); - expect(findDeployment({ network: '73799' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe111); - expect(findDeployment({ network: '11297108109' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe130); - // Incorrect filter: - expect(findDeployment({ network: '0' }, _safeDeploymentsReverse)).toBeUndefined(); + it('should return the correct deployment (filtered by network)', () => { + // Reverse chronological deployments + expect(findDeployment({ network: '1' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe100); + expect(findDeployment({ network: '73799' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe111); + expect(findDeployment({ network: '11297108109' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe130); + // Incorrect filter: + expect(findDeployment({ network: '0' }, _safeDeploymentsReverse)).toBeUndefined(); - // L2 deployments - expect(findDeployment({ network: '100' }, _safeL2Deployments)).toMatchObject(SafeL2141); - // Incorrect filter: - expect(findDeployment({ network: '0' }, _safeL2Deployments)).toBeUndefined(); - }); - it('should return the correct deployment (filtered by version and released)', () => { - // Chronological deployments - expect(findDeployment({ version: '1.3.0', released: true }, _safeDeployments)).toMatchObject(GnosisSafe130); - expect(findDeployment({ version: '1.2.0', released: true }, _safeDeployments)).toMatchObject(GnosisSafe120); - expect(findDeployment({ version: '1.1.1', released: true }, _safeDeployments)).toMatchObject(GnosisSafe111); - expect(findDeployment({ version: '1.0.0', released: true }, _safeDeployments)).toMatchObject(GnosisSafe100); - // Incorrect filter: - expect(findDeployment({ version: '1.0.0', released: false }, _safeDeployments)).toBeUndefined(); + // L2 deployments + expect(findDeployment({ network: '100' }, _SAFE_L2_DEPLOYMENTS)).toMatchObject(SafeL2141); + // Incorrect filter: + expect(findDeployment({ network: '0' }, _SAFE_L2_DEPLOYMENTS)).toBeUndefined(); + }); + it('should return the correct deployment (filtered by version and released)', () => { + // Chronological deployments + expect(findDeployment({ version: '1.3.0', released: true }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe130); + expect(findDeployment({ version: '1.2.0', released: true }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe120); + expect(findDeployment({ version: '1.1.1', released: true }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe111); + expect(findDeployment({ version: '1.0.0', released: true }, _SAFE_DEPLOYMENTS)).toMatchObject(GnosisSafe100); + // Incorrect filter: + expect(findDeployment({ version: '1.0.0', released: false }, _SAFE_DEPLOYMENTS)).toBeUndefined(); - // L2 deployments - expect(findDeployment({ version: '1.3.0', released: true }, _safeL2Deployments)).toMatchObject(GnosisSafeL2130); - expect(findDeployment({ version: '1.3.0+L2', released: true }, _safeL2Deployments)).toMatchObject( - GnosisSafeL2130, - ); - // Incorrect filter: - expect(findDeployment({ version: '1.3.0+L2', released: false }, _safeL2Deployments)).toBeUndefined(); - }); - it('should return the correct deployment (filtered by version and network)', () => { - // Reverse chronological deployments - expect(findDeployment({ version: '1.0.0', network: '1' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe100); - expect(findDeployment({ version: '1.1.1', network: '1' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe111); - expect(findDeployment({ version: '1.2.0', network: '1' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe120); - expect(findDeployment({ version: '1.3.0', network: '1' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe130); - // Incorrect filter: - expect(findDeployment({ version: '1.3.0', network: '0' }, _safeDeploymentsReverse)).toBeUndefined(); + // L2 deployments + expect( + findDeployment( + { + version: '1.3.0', + released: true, + }, + _SAFE_L2_DEPLOYMENTS, + ), + ).toMatchObject(GnosisSafeL2130); + expect(findDeployment({ version: '1.3.0+L2', released: true }, _SAFE_L2_DEPLOYMENTS)).toMatchObject( + GnosisSafeL2130, + ); + // Incorrect filter: + expect(findDeployment({ version: '1.3.0+L2', released: false }, _SAFE_L2_DEPLOYMENTS)).toBeUndefined(); + }); + it('should return the correct deployment (filtered by version and network)', () => { + // Reverse chronological deployments + expect( + findDeployment( + { + version: '1.0.0', + network: '1', + }, + _safeDeploymentsReverse, + ), + ).toMatchObject(GnosisSafe100); + expect( + findDeployment( + { + version: '1.1.1', + network: '1', + }, + _safeDeploymentsReverse, + ), + ).toMatchObject(GnosisSafe111); + expect( + findDeployment( + { + version: '1.2.0', + network: '1', + }, + _safeDeploymentsReverse, + ), + ).toMatchObject(GnosisSafe120); + expect( + findDeployment( + { + version: '1.3.0', + network: '1', + }, + _safeDeploymentsReverse, + ), + ).toMatchObject(GnosisSafe130); + // Incorrect filter: + expect(findDeployment({ version: '1.3.0', network: '0' }, _safeDeploymentsReverse)).toBeUndefined(); - // L2 deployments - expect(findDeployment({ version: '1.3.0', network: '100' }, _safeL2Deployments)).toMatchObject(GnosisSafeL2130); - expect(findDeployment({ version: '1.3.0+L2', network: '100' }, _safeL2Deployments)).toMatchObject( - GnosisSafeL2130, - ); - // Incorrect filter: - expect(findDeployment({ version: '1.3.0+L2', network: '0' }, _safeL2Deployments)).toBeUndefined(); - }); - it('should return the correct deployment (filtered by released and network)', () => { - const testUnreleasedDeploymentJson: SingletonDeploymentJSON = { - version: '', - abi: [], - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + // L2 deployments + expect( + findDeployment( + { + version: '1.3.0', + network: '100', + }, + _SAFE_L2_DEPLOYMENTS, + ), + ).toMatchObject(GnosisSafeL2130); + expect(findDeployment({ version: '1.3.0+L2', network: '100' }, _SAFE_L2_DEPLOYMENTS)).toMatchObject( + GnosisSafeL2130, + ); + // Incorrect filter: + expect(findDeployment({ version: '1.3.0+L2', network: '0' }, _SAFE_L2_DEPLOYMENTS)).toBeUndefined(); + }); + it('should return the correct deployment (filtered by released and network)', () => { + const testUnreleasedDeploymentJson: SingletonDeploymentJSON = { + version: '', + abi: [], + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': 'canonical' }, - contractName: '', - released: false, - }; - const testReleasedDeploymentJson: SingletonDeploymentJSON = { - version: '', - abi: [], - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + networkAddresses: { '1': 'canonical' }, + contractName: '', + released: false, + }; + const testReleasedDeploymentJson: SingletonDeploymentJSON = { + version: '', + abi: [], + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': 'canonical' }, - contractName: '', - released: true, // Default filter value - }; - const testUnreleasedDeployment: SingletonDeployment = { - defaultAddress: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - deployments: { - canonical: { - address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', - codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + networkAddresses: { '1': 'canonical' }, + contractName: '', + released: true, // Default filter value + }; + const testUnreleasedDeployment: SingletonDeployment = { + defaultAddress: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, }, - }, - networkAddresses: { '1': '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef' }, - released: false, - abi: [], - version: '', - contractName: '', - }; + networkAddresses: { '1': '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef' }, + released: false, + abi: [], + version: '', + contractName: '', + }; - const testDeployments = [testUnreleasedDeploymentJson, testUnreleasedDeploymentJson, testReleasedDeploymentJson]; + const testDeployments = [ + testUnreleasedDeploymentJson, + testUnreleasedDeploymentJson, + testReleasedDeploymentJson, + ]; - // Reverse chronological deployments - expect(findDeployment({ released: true, network: '1' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe100); - expect(findDeployment({ released: true, network: '246' }, _safeDeploymentsReverse)).toMatchObject(GnosisSafe111); - expect(findDeployment({ released: true, network: '11297108109' }, _safeDeploymentsReverse)).toMatchObject( - GnosisSafe130, - ); - // Incorrect filter: - expect(findDeployment({ released: true, network: '0' }, _safeDeploymentsReverse)).toBeUndefined(); - expect(findDeployment({ released: false, network: '1' }, testDeployments)).toMatchObject( - testUnreleasedDeployment, - ); + // Reverse chronological deployments + expect( + findDeployment( + { + released: true, + network: '1', + }, + _safeDeploymentsReverse, + ), + ).toMatchObject(GnosisSafe100); + expect(findDeployment({ released: true, network: '246' }, _safeDeploymentsReverse)).toMatchObject( + GnosisSafe111, + ); + expect(findDeployment({ released: true, network: '11297108109' }, _safeDeploymentsReverse)).toMatchObject( + GnosisSafe130, + ); + // Incorrect filter: + expect(findDeployment({ released: true, network: '0' }, _safeDeploymentsReverse)).toBeUndefined(); + expect(findDeployment({ released: false, network: '1' }, testDeployments)).toMatchObject( + testUnreleasedDeployment, + ); - // L2 deployments - expect(findDeployment({ released: true, network: '100' }, _safeL2Deployments)).toMatchObject(SafeL2141); - // Incorrect filter: - expect(findDeployment({ released: true, network: '0' }, _safeL2Deployments)).toBeUndefined(); - expect(findDeployment({ released: false, network: '100' }, testDeployments)).toBeUndefined(); + // L2 deployments + expect(findDeployment({ released: true, network: '100' }, _SAFE_L2_DEPLOYMENTS)).toMatchObject(SafeL2141); + // Incorrect filter: + expect(findDeployment({ released: true, network: '0' }, _SAFE_L2_DEPLOYMENTS)).toBeUndefined(); + expect(findDeployment({ released: false, network: '100' }, testDeployments)).toBeUndefined(); + }); + it('should return the correct deployment (filtered by version, released and network)', () => { + // Reverse chronological deployments + expect( + findDeployment({ version: '1.0.0', released: true, network: '1' }, _safeDeploymentsReverse), + ).toMatchObject(GnosisSafe100); + expect( + findDeployment({ version: '1.1.1', released: true, network: '246' }, _safeDeploymentsReverse), + ).toMatchObject(GnosisSafe111); + expect( + findDeployment({ version: '1.2.0', released: true, network: '73799' }, _safeDeploymentsReverse), + ).toMatchObject(GnosisSafe120); + expect( + findDeployment({ version: '1.3.0', released: true, network: '11297108109' }, _safeDeploymentsReverse), + ).toMatchObject(GnosisSafe130); + // Incorrect filter: + expect( + findDeployment({ version: '1.3.0', released: false, network: '11297108109' }, _safeDeploymentsReverse), + ).toBeUndefined(); + expect( + findDeployment({ version: '1.3.0', released: true, network: '0' }, _safeDeploymentsReverse), + ).toBeUndefined(); + expect( + findDeployment({ version: '2.0.0', released: true, network: '11297108109' }, _safeDeploymentsReverse), + ).toBeUndefined(); + + // L2 deployments + expect( + findDeployment( + { + version: '1.3.0', + released: true, + network: '100', + }, + _SAFE_L2_DEPLOYMENTS, + ), + ).toMatchObject(GnosisSafeL2130); + expect( + findDeployment({ version: '1.3.0+L2', released: true, network: '100' }, _SAFE_L2_DEPLOYMENTS), + ).toMatchObject(GnosisSafeL2130); + // Incorrect filter: + expect( + findDeployment({ version: '1.3.0+L2', released: false, network: '100' }, _SAFE_L2_DEPLOYMENTS), + ).toBeUndefined(); + expect( + findDeployment({ version: '1.3.0+L2', released: true, network: '0' }, _SAFE_L2_DEPLOYMENTS), + ).toBeUndefined(); + expect( + findDeployment({ version: '2.0.0+L2', released: true, network: '100' }, _SAFE_L2_DEPLOYMENTS), + ).toBeUndefined(); + }); }); - it('should return the correct deployment (filtered by version, released and network)', () => { - // Reverse chronological deployments - expect(findDeployment({ version: '1.0.0', released: true, network: '1' }, _safeDeploymentsReverse)).toMatchObject( - GnosisSafe100, - ); - expect( - findDeployment({ version: '1.1.1', released: true, network: '246' }, _safeDeploymentsReverse), - ).toMatchObject(GnosisSafe111); - expect( - findDeployment({ version: '1.2.0', released: true, network: '73799' }, _safeDeploymentsReverse), - ).toMatchObject(GnosisSafe120); - expect( - findDeployment({ version: '1.3.0', released: true, network: '11297108109' }, _safeDeploymentsReverse), - ).toMatchObject(GnosisSafe130); - // Incorrect filter: - expect( - findDeployment({ version: '1.3.0', released: false, network: '11297108109' }, _safeDeploymentsReverse), - ).toBeUndefined(); - expect( - findDeployment({ version: '1.3.0', released: true, network: '0' }, _safeDeploymentsReverse), - ).toBeUndefined(); - expect( - findDeployment({ version: '2.0.0', released: true, network: '11297108109' }, _safeDeploymentsReverse), - ).toBeUndefined(); - // L2 deployments - expect(findDeployment({ version: '1.3.0', released: true, network: '100' }, _safeL2Deployments)).toMatchObject( - GnosisSafeL2130, - ); - expect(findDeployment({ version: '1.3.0+L2', released: true, network: '100' }, _safeL2Deployments)).toMatchObject( - GnosisSafeL2130, - ); - // Incorrect filter: - expect( - findDeployment({ version: '1.3.0+L2', released: false, network: '100' }, _safeL2Deployments), - ).toBeUndefined(); - expect(findDeployment({ version: '1.3.0+L2', released: true, network: '0' }, _safeL2Deployments)).toBeUndefined(); - expect( - findDeployment({ version: '2.0.0+L2', released: true, network: '100' }, _safeL2Deployments), - ).toBeUndefined(); + describe('multiple format', () => { + it('should populate the networkAddresses field', () => { + const testReleasedDeploymentJson: SingletonDeploymentJSON = { + version: '', + abi: [], + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + eip155: { + address: '0xc0ffeec0ffeec0ffeec0ffeec0ffeec0ffeebabe', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + zksync: { + address: '0xbabebabebabebabebabebabebabebabebabebabe', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + networkAddresses: { '1': 'canonical', '100': 'zksync', '101': ['canonical', 'eip155'] }, + contractName: '', + released: true, // Default filter value + }; + const expectedDeployment: SingletonDeploymentV2 = { + deployments: { + canonical: { + address: '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + eip155: { + address: '0xc0ffeec0ffeec0ffeec0ffeec0ffeec0ffeebabe', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + zksync: { + address: '0xbabebabebabebabebabebabebabebabebabebabe', + codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + networkAddresses: { + '1': '0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', + '100': '0xbabebabebabebabebabebabebabebabebabebabe', + '101': ['0xbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef', '0xc0ffeec0ffeec0ffeec0ffeec0ffeec0ffeebabe'], + }, + released: true, + abi: [], + version: '', + contractName: '', + }; + + const testDeployments = [testReleasedDeploymentJson]; + + expect(findDeployment({}, testDeployments, DeploymentFormats.MULTIPLE)).toMatchObject(expectedDeployment); + }); }); }); }); diff --git a/src/accessors.ts b/src/accessors.ts index 58364b911..49b8b59f7 100644 --- a/src/accessors.ts +++ b/src/accessors.ts @@ -1,12 +1,23 @@ -import SimulateTxAccessor130 from './assets/v1.3.0/simulate_tx_accessor.json'; -import SimulateTxAccessor141 from './assets/v1.4.1/simulate_tx_accessor.json'; - -import { DeploymentFilter, SingletonDeploymentJSON, SingletonDeployment } from './types'; +import { DeploymentFilter, SingletonDeployment, DeploymentFormats, SingletonDeploymentV2 } from './types'; import { findDeployment } from './utils'; +import { _ACCESSOR_DEPLOYMENTS } from './deployments'; -// This is a sorted array (newest to oldest) -const accessorDeployments: SingletonDeploymentJSON[] = [SimulateTxAccessor141, SimulateTxAccessor130]; - +/** + * Retrieves a single simulate transaction accessor deployment based on the provided filter. + * + * @param {DeploymentFilter} [filter] - Optional filter to apply when searching for the deployment. + * @returns {SingletonDeployment | undefined} - The found deployment or undefined if no deployment matches the filter. + */ export const getSimulateTxAccessorDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, accessorDeployments); + return findDeployment(filter, _ACCESSOR_DEPLOYMENTS); +}; + +/** + * Retrieves multiple simulate transaction accessor deployments based on the provided filter. + * + * @param {DeploymentFilter} [filter] - Optional filter to apply when searching for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The found deployments in the specified format or undefined if no deployments match the filter. + */ +export const getSimulateTxAccessorDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _ACCESSOR_DEPLOYMENTS, DeploymentFormats.MULTIPLE); }; diff --git a/src/deployments.ts b/src/deployments.ts new file mode 100644 index 000000000..02f8cd8f8 --- /dev/null +++ b/src/deployments.ts @@ -0,0 +1,90 @@ +import { SingletonDeploymentJSON } from './types'; + +// This is a file where all the deployments are consolidated +// We do it in a separate file so we don't have to repeat comments about the array order and the type casting. +// We use some specific types (like `AddressType`) in the `SingletonDeploymentJSON` type, but the TypeScript cannot infer that from the JSON files. +// So we need to cast them to `SingletonDeploymentJSON` manually. The casting is valid because we have a test in `__tests__/assets.test.ts` that checks that the JSON files are valid. +// The arrays are sorted by preference, which at the moment means from the most recent version to the oldest. +// The arrays are prefixed with an underscore because they are not meant to be imported directly. + +import SimulateTxAccessor130 from './assets/v1.3.0/simulate_tx_accessor.json'; +import SimulateTxAccessor141 from './assets/v1.4.1/simulate_tx_accessor.json'; + +const _ACCESSOR_DEPLOYMENTS = [SimulateTxAccessor141, SimulateTxAccessor130] as SingletonDeploymentJSON[]; + +import ProxyFactory100 from './assets/v1.0.0/proxy_factory.json'; +import ProxyFactory111 from './assets/v1.1.1/proxy_factory.json'; +import ProxyFactory130 from './assets/v1.3.0/proxy_factory.json'; +import SafeProxyFactory141 from './assets/v1.4.1/safe_proxy_factory.json'; + +const _FACTORY_DEPLOYMENTS = [ + SafeProxyFactory141, + ProxyFactory130, + ProxyFactory111, + ProxyFactory100, +] as SingletonDeploymentJSON[]; + +import DefaultCallbackHandler130 from './assets/v1.1.1/default_callback_handler.json'; + +const _DEFAULT_CALLBACK_HANDLER_DEPLOYMENTS = [DefaultCallbackHandler130] as SingletonDeploymentJSON[]; + +import CompatibilityFallbackHandler130 from './assets/v1.3.0/compatibility_fallback_handler.json'; +import CompatibilityFallbackHandler141 from './assets/v1.4.1/compatibility_fallback_handler.json'; + +const _COMPAT_FALLBACK_HANDLER_DEPLOYMENTS = [ + CompatibilityFallbackHandler141, + CompatibilityFallbackHandler130, +] as SingletonDeploymentJSON[]; + +import Safe141 from './assets/v1.4.1/safe.json'; +import GnosisSafe130 from './assets/v1.3.0/gnosis_safe.json'; +import GnosisSafe120 from './assets/v1.2.0/gnosis_safe.json'; +import GnosisSafe111 from './assets/v1.1.1/gnosis_safe.json'; +import GnosisSafe100 from './assets/v1.0.0/gnosis_safe.json'; + +const _SAFE_DEPLOYMENTS = [ + Safe141, + GnosisSafe130, + GnosisSafe120, + GnosisSafe111, + GnosisSafe100, +] as SingletonDeploymentJSON[]; + +import SafeL2141 from './assets/v1.4.1/safe_l2.json'; +import GnosisSafeL2130 from './assets/v1.3.0/gnosis_safe_l2.json'; + +const _SAFE_L2_DEPLOYMENTS = [SafeL2141, GnosisSafeL2130] as SingletonDeploymentJSON[]; + +import MultiSend111 from './assets/v1.1.1/multi_send.json'; +import MultiSend130 from './assets/v1.3.0/multi_send.json'; +import MultiSend141 from './assets/v1.4.1/multi_send.json'; + +const _MULTI_SEND_DEPLOYMENTS = [MultiSend141, MultiSend130, MultiSend111] as SingletonDeploymentJSON[]; + +import MultiSendCallOnly130 from './assets/v1.3.0/multi_send_call_only.json'; +import MultiSendCallOnly141 from './assets/v1.4.1/multi_send_call_only.json'; + +const _MULTI_SEND_CALL_ONLY_DEPLOYMENTS = [MultiSendCallOnly141, MultiSendCallOnly130] as SingletonDeploymentJSON[]; + +import CreateCall130 from './assets/v1.3.0/create_call.json'; +import CreateCall141 from './assets/v1.4.1/create_call.json'; + +const _CREATE_CALL_DEPLOYMENTS = [CreateCall141, CreateCall130] as SingletonDeploymentJSON[]; + +import SignMessageLib130 from './assets/v1.3.0/sign_message_lib.json'; +import SignMessageLib141 from './assets/v1.4.1/sign_message_lib.json'; + +const _SIGN_MESSAGE_LIB_DEPLOYMENTS = [SignMessageLib141, SignMessageLib130] as SingletonDeploymentJSON[]; + +export { + _ACCESSOR_DEPLOYMENTS, + _FACTORY_DEPLOYMENTS, + _DEFAULT_CALLBACK_HANDLER_DEPLOYMENTS, + _COMPAT_FALLBACK_HANDLER_DEPLOYMENTS, + _SAFE_DEPLOYMENTS, + _SAFE_L2_DEPLOYMENTS, + _MULTI_SEND_DEPLOYMENTS, + _MULTI_SEND_CALL_ONLY_DEPLOYMENTS, + _CREATE_CALL_DEPLOYMENTS, + _SIGN_MESSAGE_LIB_DEPLOYMENTS, +}; diff --git a/src/factories.ts b/src/factories.ts index dcc9e1116..04828a2cc 100644 --- a/src/factories.ts +++ b/src/factories.ts @@ -1,19 +1,21 @@ -import ProxyFactory100 from './assets/v1.0.0/proxy_factory.json'; -import ProxyFactory111 from './assets/v1.1.1/proxy_factory.json'; -import ProxyFactory130 from './assets/v1.3.0/proxy_factory.json'; -import SafeProxyFactory141 from './assets/v1.4.1/safe_proxy_factory.json'; - -import { DeploymentFilter, SingletonDeployment, SingletonDeploymentJSON } from './types'; +import { DeploymentFilter, DeploymentFormats, SingletonDeployment, SingletonDeploymentV2 } from './types'; import { findDeployment } from './utils'; +import { _FACTORY_DEPLOYMENTS } from './deployments'; -// This is a sorted array (newest to oldest) -const factoryDeployments: SingletonDeploymentJSON[] = [ - SafeProxyFactory141, - ProxyFactory130, - ProxyFactory111, - ProxyFactory100, -]; - +/** + * Finds the latest proxy factory deployment that matches the given filter. + * @param {DeploymentFilter} [filter] - The filter to apply when searching for the deployment. + * @returns {SingletonDeployment | undefined} - The found deployment or undefined if no deployment matches the filter. + */ export const getProxyFactoryDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, factoryDeployments); + return findDeployment(filter, _FACTORY_DEPLOYMENTS); +}; + +/** + * Finds all proxy factory deployments that match the given filter. + * @param {DeploymentFilter} [filter] - The filter to apply when searching for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The found deployments or undefined if no deployments match the filter. + */ +export const getProxyFactoryDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _FACTORY_DEPLOYMENTS, DeploymentFormats.MULTIPLE); }; diff --git a/src/handler.ts b/src/handler.ts index 76cd32217..17b02e86f 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -1,35 +1,68 @@ -import DefaultCallbackHandler130 from './assets/v1.1.1/default_callback_handler.json'; -import CompatibilityFallbackHandler130 from './assets/v1.3.0/compatibility_fallback_handler.json'; -import CompatibilityFallbackHandler141 from './assets/v1.4.1/compatibility_fallback_handler.json'; -import { DeploymentFilter, SingletonDeployment, SingletonDeploymentJSON } from './types'; +import { DeploymentFilter, DeploymentFormats, SingletonDeployment, SingletonDeploymentV2 } from './types'; import { findDeployment } from './utils'; +import { _DEFAULT_CALLBACK_HANDLER_DEPLOYMENTS, _COMPAT_FALLBACK_HANDLER_DEPLOYMENTS } from './deployments'; -// This is a sorted array (by preference) -const defaultCallbackHandlerDeployments: SingletonDeploymentJSON[] = [DefaultCallbackHandler130]; - +/** + * Get the default callback handler deployment based on the provided filter. + * @param {DeploymentFilter} [filter] - Optional filter to apply to the deployment search. + * @returns {SingletonDeployment | undefined} - The found deployment or undefined if not found. + */ export const getDefaultCallbackHandlerDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, defaultCallbackHandlerDeployments); + return findDeployment(filter, _DEFAULT_CALLBACK_HANDLER_DEPLOYMENTS); }; -// This is a sorted array (by preference) -const compatFallbackHandlerDeployments: SingletonDeploymentJSON[] = [ - CompatibilityFallbackHandler141, - CompatibilityFallbackHandler130, -]; +/** + * Get all default callback handler deployments based on the provided filter. + * @param {DeploymentFilter} [filter] - Optional filter to apply to the deployment search. + * @returns {SingletonDeploymentV2 | undefined} - The found deployments in version 2 format or undefined if not found. + */ +export const getDefaultCallbackHandlerDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _DEFAULT_CALLBACK_HANDLER_DEPLOYMENTS, DeploymentFormats.MULTIPLE); +}; +/** + * Get the compatibility fallback handler deployment based on the provided filter. + * @param {DeploymentFilter} [filter] - Optional filter to apply to the deployment search. + * @returns {SingletonDeployment | undefined} - The found deployment or undefined if not found. + */ export const getCompatibilityFallbackHandlerDeployment = ( filter?: DeploymentFilter, ): SingletonDeployment | undefined => { - return findDeployment(filter, compatFallbackHandlerDeployments); + return findDeployment(filter, _COMPAT_FALLBACK_HANDLER_DEPLOYMENTS); }; -// This is a sorted array (by preference) -const fallbackHandlerDeployments: SingletonDeploymentJSON[] = [ - CompatibilityFallbackHandler141, - CompatibilityFallbackHandler130, - DefaultCallbackHandler130, -]; - -export const getFallbackHandlerDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, fallbackHandlerDeployments); +/** + * Get all compatibility fallback handler deployments based on the provided filter. + * @param {DeploymentFilter} [filter] - Optional filter to apply to the deployment search. + * @returns {SingletonDeploymentV2 | undefined} - The found deployments in version 2 format or undefined if not found. + */ +export const getCompatibilityFallbackHandlerDeployments = ( + filter?: DeploymentFilter, +): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _COMPAT_FALLBACK_HANDLER_DEPLOYMENTS, DeploymentFormats.MULTIPLE); }; + +/** + * Get the fallback handler deployment based on the provided filter. This method is an alias for `getCompatibilityFallbackHandlerDeployment`. + * Kept for backwards compatibility. + * @param {DeploymentFilter} [filter] - Optional filter to apply to the deployment search. + * @returns {SingletonDeployment | undefined} - The found deployment or undefined if not found. + */ +export const getFallbackHandlerDeployment = getCompatibilityFallbackHandlerDeployment; +// Leaving the comment here so it's not a part of the JSDoc +// Previously, the function code was this: +// // This is a sorted array (by preference) +// const fallbackHandlerDeployments: SingletonDeploymentJSON[] = [ +// CompatibilityFallbackHandler141, +// CompatibilityFallbackHandler130, +// DefaultCallbackHandler130, +// ]; +// export const getFallbackHandlerDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { +// return findDeployment(filter, fallbackHandlerDeployments); +// }; +// The problem with the function is that there’s no possible filter that would make the function return the last element of the array +// (DefaultCallbackHandler130 ), since we only allow to filter by version, networks and released flag. The only possible way is to have +// the default callback handler deployed to a network where the compatibility fallback handler isn’t deployed, but we require the whole +// suite of the contracts be deployed to a network. +// Since we didn't want to enforce a preferred fallback handler on the deployments package level, +// we decided to alias it to getCompatibilityFallbackHandlerDeployment (previously returned value) diff --git a/src/libs.ts b/src/libs.ts index 11819c71d..a066fbbfc 100644 --- a/src/libs.ts +++ b/src/libs.ts @@ -1,38 +1,80 @@ -import CreateCall130 from './assets/v1.3.0/create_call.json'; -import CreateCall141 from './assets/v1.4.1/create_call.json'; -import MultiSend111 from './assets/v1.1.1/multi_send.json'; -import MultiSend130 from './assets/v1.3.0/multi_send.json'; -import MultiSend141 from './assets/v1.4.1/multi_send.json'; -import MultiSendCallOnly130 from './assets/v1.3.0/multi_send_call_only.json'; -import MultiSendCallOnly141 from './assets/v1.4.1/multi_send_call_only.json'; -import SignMessageLib130 from './assets/v1.3.0/sign_message_lib.json'; -import SignMessageLib141 from './assets/v1.4.1/sign_message_lib.json'; -import { DeploymentFilter, SingletonDeployment, SingletonDeploymentJSON } from './types'; +import { + _CREATE_CALL_DEPLOYMENTS, + _MULTI_SEND_CALL_ONLY_DEPLOYMENTS, + _MULTI_SEND_DEPLOYMENTS, + _SIGN_MESSAGE_LIB_DEPLOYMENTS, +} from './deployments'; +import { DeploymentFilter, DeploymentFormats, SingletonDeployment, SingletonDeploymentV2 } from './types'; import { findDeployment } from './utils'; -// This is a sorted array (by preference, currently we use 111 in most cases) -const multiSendDeployments: SingletonDeploymentJSON[] = [MultiSend141, MultiSend130, MultiSend111]; - +/** + * Get the MultiSend deployment based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployment. + * @returns {SingletonDeployment | undefined} - The matched deployment or undefined if not found. + */ export const getMultiSendDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, multiSendDeployments); + return findDeployment(filter, _MULTI_SEND_DEPLOYMENTS); }; -// This is a sorted array (by preference) -const multiSendCallOnlyDeployments: SingletonDeploymentJSON[] = [MultiSendCallOnly141, MultiSendCallOnly130]; +/** + * Get all MultiSend deployments based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The matched deployments or undefined if not found. + */ +export const getMultiSendDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _MULTI_SEND_DEPLOYMENTS, DeploymentFormats.MULTIPLE); +}; +/** + * Get the MultiSendCallOnly deployment based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployment. + * @returns {SingletonDeployment | undefined} - The matched deployment or undefined if not found. + */ export const getMultiSendCallOnlyDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, multiSendCallOnlyDeployments); + return findDeployment(filter, _MULTI_SEND_CALL_ONLY_DEPLOYMENTS); }; -// This is a sorted array (by preference) -const createCallDeployments: SingletonDeploymentJSON[] = [CreateCall141, CreateCall130]; +/** + * Get all MultiSendCallOnly deployments based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The matched deployments or undefined if not found. + */ +export const getMultiSendCallOnlyDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _MULTI_SEND_CALL_ONLY_DEPLOYMENTS, DeploymentFormats.MULTIPLE); +}; +/** + * Get the CreateCall deployment based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployment. + * @returns {SingletonDeployment | undefined} - The matched deployment or undefined if not found. + */ export const getCreateCallDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, createCallDeployments); + return findDeployment(filter, _CREATE_CALL_DEPLOYMENTS); }; -const signMessageLibDeployments: SingletonDeploymentJSON[] = [SignMessageLib141, SignMessageLib130]; +/** + * Get all CreateCall deployments based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The matched deployments or undefined if not found. + */ +export const getCreateCallDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _CREATE_CALL_DEPLOYMENTS, DeploymentFormats.MULTIPLE); +}; +/** + * Get the SignMessageLib deployment based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployment. + * @returns {SingletonDeployment | undefined} - The matched deployment or undefined if not found. + */ export const getSignMessageLibDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, signMessageLibDeployments); + return findDeployment(filter, _SIGN_MESSAGE_LIB_DEPLOYMENTS); +}; + +/** + * Get all SignMessageLib deployments based on the provided filter. + * @param {DeploymentFilter} [filter] - The filter criteria for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The matched deployments or undefined if not found. + */ +export const getSignMessageLibDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _SIGN_MESSAGE_LIB_DEPLOYMENTS, DeploymentFormats.MULTIPLE); }; diff --git a/src/safes.ts b/src/safes.ts index 67b18d5f1..d54cfa254 100644 --- a/src/safes.ts +++ b/src/safes.ts @@ -1,29 +1,39 @@ -import SafeL2141 from './assets/v1.4.1/safe_l2.json'; -import Safe141 from './assets/v1.4.1/safe.json'; -import GnosisSafeL2130 from './assets/v1.3.0/gnosis_safe_l2.json'; -import GnosisSafe130 from './assets/v1.3.0/gnosis_safe.json'; -import GnosisSafe120 from './assets/v1.2.0/gnosis_safe.json'; -import GnosisSafe111 from './assets/v1.1.1/gnosis_safe.json'; -import GnosisSafe100 from './assets/v1.0.0/gnosis_safe.json'; -import { DeploymentFilter, SingletonDeployment, SingletonDeploymentJSON } from './types'; +import { _SAFE_DEPLOYMENTS, _SAFE_L2_DEPLOYMENTS } from './deployments'; +import { DeploymentFilter, DeploymentFormats, SingletonDeployment, SingletonDeploymentV2 } from './types'; import { findDeployment } from './utils'; -// This is a sorted array (newest to oldest), exported for tests -export const _safeDeployments: SingletonDeploymentJSON[] = [ - Safe141, - GnosisSafe130, - GnosisSafe120, - GnosisSafe111, - GnosisSafe100, -]; - +/** + * Finds the latest safe singleton deployment that matches the given filter. + * @param {DeploymentFilter} [filter] - The filter to apply when searching for the deployment. + * @returns {SingletonDeployment | undefined} - The found deployment or undefined if no deployment matches the filter. + */ export const getSafeSingletonDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, _safeDeployments); + return findDeployment(filter, _SAFE_DEPLOYMENTS); }; -// This is a sorted array (newest to oldest), exported for tests -export const _safeL2Deployments: SingletonDeploymentJSON[] = [SafeL2141, GnosisSafeL2130]; +/** + * Finds all safe singleton deployments that match the given filter. + * @param {DeploymentFilter} [filter] - The filter to apply when searching for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The found deployments or undefined if no deployments match the filter. + */ +export const getSafeSingletonDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _SAFE_DEPLOYMENTS, DeploymentFormats.MULTIPLE); +}; +/** + * Finds the latest safe L2 singleton deployment that matches the given filter. + * @param {DeploymentFilter} [filter] - The filter to apply when searching for the deployment. + * @returns {SingletonDeployment | undefined} - The found deployment or undefined if no deployment matches the filter. + */ export const getSafeL2SingletonDeployment = (filter?: DeploymentFilter): SingletonDeployment | undefined => { - return findDeployment(filter, _safeL2Deployments); + return findDeployment(filter, _SAFE_L2_DEPLOYMENTS); +}; + +/** + * Finds all safe L2 singleton deployments that match the given filter. + * @param {DeploymentFilter} [filter] - The filter to apply when searching for the deployments. + * @returns {SingletonDeploymentV2 | undefined} - The found deployments or undefined if no deployments match the filter. + */ +export const getSafeL2SingletonDeployments = (filter?: DeploymentFilter): SingletonDeploymentV2 | undefined => { + return findDeployment(filter, _SAFE_L2_DEPLOYMENTS, DeploymentFormats.MULTIPLE); }; diff --git a/src/types.ts b/src/types.ts index 740e8ae99..acc3fe04c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,16 +1,15 @@ -enum AddressType { - // The original address the contract was deployed on. - // Starting with 1.4.1, the contracts are deployed with an EIP155-compatible transaction. - CANONICAL = 'canonical', +export type AddressType = 'canonical' | 'eip155' | 'zksync'; - // An address that was deployed with a transaction compatible with the EIP155 standard. - // The type is only used for 1.3.0 deployments. - EIP155 = 'eip155', +export const enum DeploymentFormats { + // The old format that only allows a single address for each network. + SINGLETON = 'singleton', - // An address that is deployed to the ZkSync VM networks. - ZKSYNC = 'zksync', + // The new format that allows multiple addresses for each network. + MULTIPLE = 'multiple', } +type AtLeastOne }> = Partial & U[keyof U]; + export interface SingletonDeploymentJSON { // Indicates if the deployment is released. released: boolean; @@ -30,10 +29,10 @@ export interface SingletonDeploymentJSON { // 1.3.0: canonical, eip155, zksync // 1.4.1: canonical, zksync // Ex: deployments: { "canonical": { "codeHash": "0x1234", "address": "0x5678"}} - deployments: Record>; + deployments: AtLeastOne>; // A record of network addresses, where the key is the network identifier and the value is either a single address type or an array of address types. - networkAddresses: Record; + networkAddresses: Record; // The ABI (Application Binary Interface) of the contract. /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -57,8 +56,14 @@ export interface SingletonDeployment { // The address & hash of the contract code, where the key is the deployment type. // There could be multiple deployment types: canonical, eip155, zksync + // Possible addresses per version: + // 1.0.0: canonical + // 1.1.1: canonical + // 1.2.0: canonical + // 1.3.0: canonical, eip155, zksync + // 1.4.1: canonical, zksync // Ex: deployments: { "canonical": { "codeHash": "0x1234", "address": "0x5678"}} - deployments: Record>; + deployments: AtLeastOne>; // A record of network addresses, where the key is the network identifier and the value is the address. networkAddresses: Record; @@ -72,10 +77,10 @@ export interface SingletonDeploymentV2 { released: boolean; contractName: string; version: string; - deployments: Record>; + deployments: AtLeastOne>; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ abi: any[]; - networkAddresses: Record; + networkAddresses: Record; } export interface DeploymentFilter { diff --git a/src/utils.ts b/src/utils.ts index fa04d8246..c1edaa25f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,11 @@ -import { DeploymentFilter, SingletonDeployment, SingletonDeploymentJSON } from './types'; +import { + DeploymentFilter, + SingletonDeployment, + SingletonDeploymentJSON, + DeploymentFormats, + SingletonDeploymentV2, + AddressType, +} from './types'; import semverSatisfies from 'semver/functions/satisfies'; const DEFAULT_FILTER: DeploymentFilter = { released: true }; @@ -18,30 +25,68 @@ const mapJsonToDeploymentsFormatV1 = (deployment: SingletonDeploymentJSON): Sing const defaultAddressType = Array.isArray(deployment.networkAddresses[DEFAULT_NETWORK_CHAIN_ID]) ? deployment.networkAddresses[DEFAULT_NETWORK_CHAIN_ID][0] : deployment.networkAddresses[DEFAULT_NETWORK_CHAIN_ID]; - const defaultAddress = deployment.deployments[defaultAddressType].address; + // The usage of non-null assertion below is safe, because we validate that the asset files are properly formed in tests + const defaultAddress = deployment.deployments[defaultAddressType]!.address; const networkAddresses = Object.fromEntries( Object.entries(deployment.networkAddresses).map(([chainId, addressTypes]) => [ chainId, Array.isArray(addressTypes) - ? deployment.deployments[addressTypes[0]].address - : deployment.deployments[addressTypes].address, + ? deployment.deployments[addressTypes[0]]!.address + : deployment.deployments[addressTypes]!.address, ]), ); return { ...deployment, defaultAddress, networkAddresses }; }; +/** + * Maps a SingletonDeploymentJSON object to a SingletonDeploymentV2 object. + * + * This function transforms the `networkAddresses` field of the deployment JSON object. + * It converts each entry in `networkAddresses` to an array of addresses, using the `addresses` field + * to resolve each address type. + * + * @param {SingletonDeploymentJSON} deployment - The deployment JSON object to map. + * @returns {SingletonDeploymentV2} - The mapped deployment object in V2 format. + */ +const mapJsonToDeploymentsFormatV2 = (deployment: SingletonDeploymentJSON): SingletonDeploymentV2 => { + const newJson = { ...deployment }; + newJson.networkAddresses = Object.fromEntries( + Object.entries(deployment.networkAddresses).map(([chainId, addressTypes]) => [ + chainId, + (Array.isArray(addressTypes) + ? // The usage of non-null assertion below is safe, because we validate that the asset files are properly formed in tests + (addressTypes.map((addressType) => deployment.deployments[addressType]!.address) as AddressType[]) + : deployment.deployments[addressTypes]!.address) as AddressType, + ]), + ); + + return newJson; +}; + /** * Finds a deployment that matches the given criteria. + * This function is implemented as a regular function to allow for overloading: https://github.com/microsoft/TypeScript/issues/33482 * * @param {DeploymentFilter} [criteria=DEFAULT_FILTER] - The filter criteria to match deployments. * @param {SingletonDeploymentJSON[]} deployments - The list of deployment JSON objects to search. * @returns {SingletonDeployment | undefined} - The found deployment object or undefined if no match is found. */ -export const findDeployment = ( - criteria: DeploymentFilter = DEFAULT_FILTER, +function findDeployment( + criteria: DeploymentFilter | undefined, + deployments: SingletonDeploymentJSON[], + format?: DeploymentFormats.SINGLETON, +): SingletonDeployment | undefined; +function findDeployment( + criteria: DeploymentFilter | undefined, deployments: SingletonDeploymentJSON[], -): SingletonDeployment | undefined => { + format: DeploymentFormats.MULTIPLE, +): SingletonDeploymentV2 | undefined; +function findDeployment( + criteria = DEFAULT_FILTER, + deployments: SingletonDeploymentJSON[], + format: DeploymentFormats = DeploymentFormats.SINGLETON, +): SingletonDeployment | SingletonDeploymentV2 | undefined { const { version, released, network } = { ...DEFAULT_FILTER, ...criteria }; const deploymentJson = deployments.find((deployment) => { @@ -52,5 +97,13 @@ export const findDeployment = ( return true; }); - return deploymentJson ? mapJsonToDeploymentsFormatV1(deploymentJson) : undefined; -}; + if (!deploymentJson) return undefined; + + if (format === DeploymentFormats.MULTIPLE) { + return mapJsonToDeploymentsFormatV2(deploymentJson); + } else { + return mapJsonToDeploymentsFormatV1(deploymentJson); + } +} + +export { findDeployment };