From de264c9a8b9307188b17f88955872639c970cc5c Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 30 Jun 2025 18:04:09 +0300 Subject: [PATCH 01/37] scaffold deploy function --- .dagger/.gitattributes | 1 + .dagger/.gitignore | 4 ++ .dagger/package.json | 7 +++ .dagger/src/index.ts | 109 +++++++++++++++++++++++++++++++++++++++++ .dagger/tsconfig.json | 13 +++++ .dagger/yarn.lock | 8 +++ dagger.json | 8 +++ 7 files changed, 150 insertions(+) create mode 100644 .dagger/.gitattributes create mode 100644 .dagger/.gitignore create mode 100644 .dagger/package.json create mode 100644 .dagger/src/index.ts create mode 100644 .dagger/tsconfig.json create mode 100644 .dagger/yarn.lock create mode 100644 dagger.json diff --git a/.dagger/.gitattributes b/.dagger/.gitattributes new file mode 100644 index 000000000..827418463 --- /dev/null +++ b/.dagger/.gitattributes @@ -0,0 +1 @@ +/sdk/** linguist-generated diff --git a/.dagger/.gitignore b/.dagger/.gitignore new file mode 100644 index 000000000..040187c61 --- /dev/null +++ b/.dagger/.gitignore @@ -0,0 +1,4 @@ +/sdk +/**/node_modules/** +/**/.pnpm-store/** +/.env diff --git a/.dagger/package.json b/.dagger/package.json new file mode 100644 index 000000000..3184033a9 --- /dev/null +++ b/.dagger/package.json @@ -0,0 +1,7 @@ +{ + "type": "module", + "dependencies": { + "typescript": "^5.5.4" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts new file mode 100644 index 000000000..82df33364 --- /dev/null +++ b/.dagger/src/index.ts @@ -0,0 +1,109 @@ +/** + * A generated module for LifiContracts functions + * + * This module provides deployment functions for LiFi smart contracts + * using Foundry in containerized environments. + */ +import { dag, Container, Directory, object, func } from '@dagger.io/dagger' + +@object() +export class LifiContracts { + /** + * Deploy a smart contract using Foundry forge script + * + * @param source - Source directory containing the contract code (should include .env file) + * @param scriptPath - Path to the deployment script (e.g., "script/deploy/facets/DeployExecutor.s.sol") + * @param network - Target network name (e.g., "mainnet", "polygon", "arbitrum") + * @param deploySalt - Salt for CREATE3 deployment + * @param create3FactoryAddress - Address of the CREATE3 factory contract + * @param fileSuffix - File suffix for deployment logs (e.g., "staging", "production") + * @param solcVersion - Solidity compiler version (e.g., "0.8.29") + * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") + * @param gasEstimateMultiplier - Gas estimate multiplier percentage (default: "130") + * @param diamondType - Type of diamond contract ("LiFiDiamond" or "LiFiDiamondImmutable") + * @param broadcast - Whether to broadcast the transaction (default: true) + * @param legacy - Whether to use legacy transaction type (default: true) + * @param slow - Whether to use slow mode for better reliability (default: true) + * @param skipSimulation - Whether to skip simulation (default: false) + * @param verbosity - Verbosity level (default: "vvvvv") + */ + @func() + deployContract( + source: Directory, + scriptPath: string, + network: string, + deploySalt: string, + create3FactoryAddress: string, + fileSuffix: string, + solcVersion?: string, + evmVersion?: string, + gasEstimateMultiplier?: string, + diamondType?: string, + broadcast?: boolean, + legacy?: boolean, + slow?: boolean, + skipSimulation?: boolean, + verbosity?: string + ): Container { + // Set default values + const gasMultiplier = gasEstimateMultiplier || '130' + const shouldBroadcast = broadcast !== false + const useLegacy = legacy !== false + const useSlow = slow !== false + const shouldSkipSimulation = skipSimulation === true + const logLevel = verbosity || 'vvvvv' + const solc = solcVersion || '0.8.29' + const evm = evmVersion || 'cancun' + + // Build forge script command + const forgeArgs = [ + 'forge', + 'script', + scriptPath, + '-f', + network, + '--use', + solc, + '--evm-version', + evm, + `-${logLevel}`, + '--json', + ] + + // Add conditional flags + if (shouldBroadcast) forgeArgs.push('--broadcast') + if (useLegacy) forgeArgs.push('--legacy') + if (useSlow) forgeArgs.push('--slow') + if (shouldSkipSimulation) forgeArgs.push('--skip-simulation') + + // Add gas estimate multiplier + forgeArgs.push('--gas-estimate-multiplier', gasMultiplier) + + // Start with foundry container + let container = dag + .container() + .from('ghcr.io/foundry-rs/foundry:latest') + .withMountedDirectory('/workspace', source) + .withWorkdir('/workspace') + // Set required environment variables + .withEnvVariable('DEPLOYSALT', deploySalt) + .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) + .withEnvVariable('NETWORK', network) + .withEnvVariable('FILE_SUFFIX', fileSuffix) + + // Set optional environment variables if provided + if (diamondType) { + container = container.withEnvVariable('DIAMOND_TYPE', diamondType) + } + + // Build command that sources .env file and runs forge + const forgeCommand = [ + 'sh', + '-c', + `test -f .env && source .env; ${forgeArgs.join(' ')}`, + ] + + // Execute the forge script command with .env sourcing + return container.withExec(forgeCommand) + } +} diff --git a/.dagger/tsconfig.json b/.dagger/tsconfig.json new file mode 100644 index 000000000..4ec0c2da6 --- /dev/null +++ b/.dagger/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "moduleResolution": "Node", + "experimentalDecorators": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "@dagger.io/dagger": ["./sdk/index.ts"], + "@dagger.io/dagger/telemetry": ["./sdk/telemetry.ts"] + } + } +} diff --git a/.dagger/yarn.lock b/.dagger/yarn.lock new file mode 100644 index 000000000..ab49665af --- /dev/null +++ b/.dagger/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^5.5.4: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== diff --git a/dagger.json b/dagger.json new file mode 100644 index 000000000..4ac9def5b --- /dev/null +++ b/dagger.json @@ -0,0 +1,8 @@ +{ + "name": "lifi-contracts", + "engineVersion": "v0.18.10", + "sdk": { + "source": "typescript" + }, + "source": ".dagger" +} From a6d06153cad4d94a81bc5b31e2b17a1a90ecc0b5 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 4 Jul 2025 18:18:04 +0300 Subject: [PATCH 02/37] more comprehensive deployment --- .dagger/bun.lock | 16 + .dagger/package.json | 1 + .dagger/src/index.ts | 730 +++++++++++++++++++++++++++++++++++++++++-- .dagger/yarn.lock | 8 - 4 files changed, 725 insertions(+), 30 deletions(-) create mode 100644 .dagger/bun.lock delete mode 100644 .dagger/yarn.lock diff --git a/.dagger/bun.lock b/.dagger/bun.lock new file mode 100644 index 000000000..1d8c21f09 --- /dev/null +++ b/.dagger/bun.lock @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "dependencies": { + "dotenv": "^17.0.1", + "typescript": "^5.5.4", + }, + }, + }, + "packages": { + "dotenv": ["dotenv@17.0.1", "", {}, "sha512-GLjkduuAL7IMJg/ZnOPm9AnWKJ82mSE2tzXLaJ/6hD6DhwGfZaXG77oB8qbReyiczNxnbxQKyh0OE5mXq0bAHA=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + } +} diff --git a/.dagger/package.json b/.dagger/package.json index 3184033a9..54daa2754 100644 --- a/.dagger/package.json +++ b/.dagger/package.json @@ -1,6 +1,7 @@ { "type": "module", "dependencies": { + "dotenv": "^17.0.1", "typescript": "^5.5.4" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 82df33364..5a9fe1801 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -4,28 +4,88 @@ * This module provides deployment functions for LiFi smart contracts * using Foundry in containerized environments. */ -import { dag, Container, Directory, object, func } from '@dagger.io/dagger' +import { + dag, + Container, + Directory, + Secret, + object, + func, +} from '@dagger.io/dagger' + +interface NetworkConfig { + chainId: number + rpcUrl: string + deployedWithEvmVersion: string + deployedWithSolcVersion: string + verificationType: string + explorerApiUrl: string + create3Factory: string +} @object() export class LifiContracts { + /** + * Build the Foundry project using forge build + * + * @param source - Source directory containing the project root + * @param uid - User ID to match host user (optional) + * @param gid - Group ID to match host group (optional) + */ + @func() + buildProject(source: Directory): Container { + let container = dag + .container() + .from('ghcr.io/foundry-rs/foundry:latest') + .withDirectory('/workspace/src', source.directory('src')) + .withDirectory('/workspace/lib', source.directory('lib')) + .withDirectory('/workspace/script', source.directory('script')) + .withFile('/workspace/foundry.toml', source.file('foundry.toml')) + .withFile('/workspace/remappings.txt', source.file('remappings.txt')) + .withFile('/workspace/.env', source.file('.env')) + .withWorkdir('/workspace') + .withUser('root') + .withExec([ + 'mkdir', + '-p', + '/workspace/out', + '/workspace/cache', + '/workspace/broadcast', + ]) + .withExec([ + 'chown', + 'foundry:foundry', + '/workspace/out', + '/workspace/cache', + '/workspace/broadcast', + ]) + .withUser('foundry') + .withExec(['forge', 'build']) + + return container + } + /** * Deploy a smart contract using Foundry forge script * - * @param source - Source directory containing the contract code (should include .env file) + * @param source - Source directory containing the project root * @param scriptPath - Path to the deployment script (e.g., "script/deploy/facets/DeployExecutor.s.sol") * @param network - Target network name (e.g., "mainnet", "polygon", "arbitrum") * @param deploySalt - Salt for CREATE3 deployment * @param create3FactoryAddress - Address of the CREATE3 factory contract * @param fileSuffix - File suffix for deployment logs (e.g., "staging", "production") + * @param privateKey - Private key secret for deployment * @param solcVersion - Solidity compiler version (e.g., "0.8.29") * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") * @param gasEstimateMultiplier - Gas estimate multiplier percentage (default: "130") - * @param diamondType - Type of diamond contract ("LiFiDiamond" or "LiFiDiamondImmutable") * @param broadcast - Whether to broadcast the transaction (default: true) * @param legacy - Whether to use legacy transaction type (default: true) * @param slow - Whether to use slow mode for better reliability (default: true) * @param skipSimulation - Whether to skip simulation (default: false) * @param verbosity - Verbosity level (default: "vvvvv") + * @param defaultDiamondAddressDeploysalt - Default diamond address deploy salt (optional) + * @param deployToDefaultDiamondAddress - Whether to deploy to default diamond address (optional) + * @param diamondType - Diamond type for CelerIMFacet (optional) */ @func() deployContract( @@ -35,15 +95,18 @@ export class LifiContracts { deploySalt: string, create3FactoryAddress: string, fileSuffix: string, + privateKey: Secret, solcVersion?: string, evmVersion?: string, gasEstimateMultiplier?: string, - diamondType?: string, broadcast?: boolean, legacy?: boolean, slow?: boolean, skipSimulation?: boolean, - verbosity?: string + verbosity?: string, + defaultDiamondAddressDeploysalt?: string, + deployToDefaultDiamondAddress?: string, + diamondType?: string ): Container { // Set default values const gasMultiplier = gasEstimateMultiplier || '130' @@ -51,10 +114,18 @@ export class LifiContracts { const useLegacy = legacy !== false const useSlow = slow !== false const shouldSkipSimulation = skipSimulation === true - const logLevel = verbosity || 'vvvvv' const solc = solcVersion || '0.8.29' const evm = evmVersion || 'cancun' + // Build the project first + const builtContainer = this.buildProject(source) + + // Mount the deployments directory to the built container + const containerWithDeployments = builtContainer.withMountedDirectory( + '/workspace/deployments', + source.directory('deployments') + ) + // Build forge script command const forgeArgs = [ 'forge', @@ -66,10 +137,14 @@ export class LifiContracts { solc, '--evm-version', evm, - `-${logLevel}`, '--json', ] + // Add verbosity flag only if specified + if (verbosity) { + forgeArgs.splice(-1, 0, `-${verbosity}`) + } + // Add conditional flags if (shouldBroadcast) forgeArgs.push('--broadcast') if (useLegacy) forgeArgs.push('--legacy') @@ -79,31 +154,642 @@ export class LifiContracts { // Add gas estimate multiplier forgeArgs.push('--gas-estimate-multiplier', gasMultiplier) - // Start with foundry container - let container = dag - .container() - .from('ghcr.io/foundry-rs/foundry:latest') - .withMountedDirectory('/workspace', source) - .withWorkdir('/workspace') - // Set required environment variables + // Set required environment variables that the deployment scripts expect + let deployContainer = containerWithDeployments .withEnvVariable('DEPLOYSALT', deploySalt) .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) .withEnvVariable('NETWORK', network) .withEnvVariable('FILE_SUFFIX', fileSuffix) + .withSecretVariable('PRIVATE_KEY', privateKey) + .withEnvVariable( + 'DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS', + deployToDefaultDiamondAddress || 'true' + ) + + // Add optional environment variables if provided + if (defaultDiamondAddressDeploysalt) { + deployContainer = deployContainer.withEnvVariable( + 'DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT', + defaultDiamondAddressDeploysalt + ) + } - // Set optional environment variables if provided if (diamondType) { - container = container.withEnvVariable('DIAMOND_TYPE', diamondType) + deployContainer = deployContainer.withEnvVariable( + 'DIAMOND_TYPE', + diamondType + ) + } + + // Execute the forge script command + return deployContainer.withExec(forgeArgs) + } + + /** + * Verify a smart contract using Foundry forge verify-contract + * + * This function provides containerized contract verification using the same + * built environment as deployment. It can be used as an alternative to the + * traditional verifyContract bash function. + * + * @param source - Source directory containing the project root + * @param contractName - Name of the contract to verify (e.g., "Executor") + * @param contractAddress - Deployed contract address + * @param constructorArgs - Constructor arguments in hex format (e.g., "0x123...") + * @param chainId - Chain ID for verification + * @param contractFilePath - Custom contract file path (optional, auto-detected if not provided) + * @param apiKey - API key for verification service (optional) + * @param verifier - Verification service ("etherscan", "blockscout", "sourcify") (default: "etherscan") + * @param solcVersion - Solidity compiler version (e.g., "0.8.29") + * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") + * @param watch - Whether to watch verification status (default: true) + * @param skipIsVerifiedCheck - Whether to skip already verified check (default: true) + */ + @func() + verifyContract( + source: Directory, + contractName: string, + contractAddress: string, + constructorArgs: string, + chainId: string, + contractFilePath?: string, + apiKey?: string, + verifier?: string, + solcVersion?: string, + evmVersion?: string, + watch?: boolean, + skipIsVerifiedCheck?: boolean + ): Container { + // Set default values + const verificationService = verifier || 'etherscan' + const shouldWatch = watch !== false + const shouldSkipVerifiedCheck = skipIsVerifiedCheck !== false + const solc = solcVersion || '0.8.29' + const evm = evmVersion || 'cancun' + + // Build the project first using the same parameters as deployment + let container = dag + .container() + .from('ghcr.io/foundry-rs/foundry:latest') + .withDirectory('/workspace/src', source.directory('src')) + .withDirectory('/workspace/lib', source.directory('lib')) + .withDirectory('/workspace/script', source.directory('script')) + .withFile('/workspace/foundry.toml', source.file('foundry.toml')) + .withFile('/workspace/remappings.txt', source.file('remappings.txt')) + .withFile('/workspace/.env', source.file('.env')) + .withWorkdir('/workspace') + .withUser('root') + .withExec([ + 'mkdir', + '-p', + '/workspace/out', + '/workspace/cache', + '/workspace/broadcast', + ]) + .withExec([ + 'chown', + 'foundry:foundry', + '/workspace/out', + '/workspace/cache', + '/workspace/broadcast', + ]) + .withUser('foundry') + .withExec(['forge', 'build', '--use', solc, '--evm-version', evm]) + + // Mount the deployments directory + const builtContainer = container.withMountedDirectory( + '/workspace/deployments', + source.directory('deployments') + ) + + // Determine contract file path - use provided path or auto-detect + let finalContractFilePath: string + + if (contractFilePath) { + finalContractFilePath = contractFilePath + } else { + // Auto-detect based on contract name - follows pattern from getContractFilePath + // The helper function searches in src/ directory, so we need to construct the full path + // Common locations: src/Facets/, src/Periphery/, src/ + finalContractFilePath = `src/Facets/${contractName}.sol:${contractName}` + + // For some contracts like LiFiDiamond, they're directly in src/ + if ( + contractName === 'LiFiDiamond' || + contractName === 'LiFiDiamondImmutable' + ) { + finalContractFilePath = `src/${contractName}.sol:${contractName}` + } + // Periphery contracts are in src/Periphery/ + else if ( + contractName.includes('Receiver') || + contractName.includes('Executor') || + contractName.includes('FeeCollector') || + contractName.includes('ERC20Proxy') + ) { + finalContractFilePath = `src/Periphery/${contractName}.sol:${contractName}` + } + } + + // Build base verification command + const forgeArgs = ['forge', 'verify-contract'] + + // Add watch flag + if (shouldWatch) { + forgeArgs.push('--watch') + } + + // Add chain ID + forgeArgs.push('--chain', chainId) + + // Add contract address and path + forgeArgs.push(contractAddress, finalContractFilePath) + + // Add skip verification check flag + if (shouldSkipVerifiedCheck) { + forgeArgs.push('--skip-is-verified-check') + } + + // Add constructor args if present + if (constructorArgs && constructorArgs !== '0x') { + forgeArgs.push('--constructor-args', constructorArgs) + } + + // Add verifier + if (verificationService !== 'etherscan') { + forgeArgs.push('--verifier', verificationService) + } + + // Set up container with environment variables + let verifyContainer = builtContainer + + // Add API key if provided and not using sourcify + if (apiKey && verificationService !== 'sourcify') { + verifyContainer = verifyContainer.withEnvVariable( + 'ETHERSCAN_API_KEY', + apiKey + ) + forgeArgs.push('-e', 'ETHERSCAN_API_KEY') } - // Build command that sources .env file and runs forge - const forgeCommand = [ + // Execute the verification command + return verifyContainer.withExec(forgeArgs) + } + + /** + * Verify a smart contract with simplified interface matching bash script + * + * This function provides a simplified interface that matches the existing + * verifyContract bash function signature for easier integration. + * + * @param source - Source directory containing the project root + * @param network - Target network name (e.g., "mainnet", "polygon", "arbitrum") + * @param contractName - Name of the contract to verify (e.g., "Executor") + * @param contractAddress - Deployed contract address + * @param constructorArgs - Constructor arguments in hex format (e.g., "0x123...") + * @param solcVersion - Solidity compiler version (e.g., "0.8.29") + * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") + */ + @func() + verifyContractSimple( + source: Directory, + network: string, + contractName: string, + contractAddress: string, + constructorArgs: string, + solcVersion?: string, + evmVersion?: string + ): Container { + const solc = solcVersion || '0.8.29' + const evm = evmVersion || 'cancun' + + // Create a mapping of network names to chain IDs + // This should ideally read from networks.json, but for now we'll use a static mapping + const networkToChainId: { [key: string]: string } = { + mainnet: '1', + arbitrum: '42161', + polygon: '137', + optimism: '10', + base: '8453', + avalanche: '43114', + bsc: '56', + fantom: '250', + gnosis: '100', + celo: '42220', + moonbeam: '1284', + moonriver: '1285', + aurora: '1313161554', + cronos: '25', + fuse: '122', + metis: '1088', + boba: '288', + viction: '88', + mantle: '5000', + linea: '59144', + scroll: '534352', + zksync: '324', + polygonzkevm: '1101', + mode: '34443', + blast: '81457', + fraxtal: '252', + sei: '1329', + taiko: '167000', + worldchain: '480', + sonic: '146', + unichain: '130', + abstract: '2741', + apechain: '33139', + berachain: '80094', + bob: '60808', + corn: '21000000', + etherlink: '42793', + flare: '14', + gravity: '1625', + hyperevm: '999', + immutablezkevm: '13371', + ink: '57073', + kaia: '8217', + katana: '747474', + lens: '232', + lisk: '1135', + nibiru: '6900', + opbnb: '204', + plume: '98866', + rootstock: '30', + soneium: '1868', + superposition: '55244', + swellchain: '1923', + vana: '1480', + xdc: '50', + xlayer: '196', + } + + const chainId = networkToChainId[network] || '1' // default to mainnet if network not found + + return this.verifyContract( + source, + contractName, + contractAddress, + constructorArgs, + chainId, + undefined, // auto-detect contract file path + undefined, // API key should be determined from network config + 'etherscan', // default verifier + solc, + evm + ) + } + /** + * Deploy a smart contract with simplified interface matching bash script + * + * This function provides a simplified interface that matches the existing + * forge script execution in the bash deployment scripts. + * + * @param source - Source directory containing the project root + * @param scriptPath - Path to the deployment script + * @param network - Target network name + * @param environment - Environment (staging/production) + * @param privateKey - Private key secret for deployment + * @param deploySalt - Salt for CREATE3 deployment + * @param create3FactoryAddress - Address of the CREATE3 factory contract + * @param gasEstimateMultiplier - Gas estimate multiplier (optional) + * @param defaultDiamondAddressDeploysalt - Default diamond address deploy salt (optional) + * @param deployToDefaultDiamondAddress - Whether to deploy to default diamond address (optional) + * @param diamondType - Diamond type for CelerIMFacet (optional) + */ + @func() + deployContractSimple( + source: Directory, + scriptPath: string, + network: string, + environment: string, + privateKey: Secret, + deploySalt: string, + create3FactoryAddress: string, + gasEstimateMultiplier?: string, + defaultDiamondAddressDeploysalt?: string, + deployToDefaultDiamondAddress?: string, + diamondType?: string + ): Container { + // Determine file suffix based on environment + const fileSuffix = environment === 'production' ? '' : 'staging.' + + return this.deployContract( + source, + scriptPath, + network, + deploySalt, + create3FactoryAddress, + fileSuffix, + privateKey, + '0.8.29', // default solc version + 'cancun', // default evm version + gasEstimateMultiplier, + true, // broadcast + true, // legacy + true, // slow + false, // skipSimulation + undefined, // verbosity - omit by default + defaultDiamondAddressDeploysalt, + deployToDefaultDiamondAddress, + diamondType + ) + } + + /** + * Deploy a smart contract with advanced configuration reading from networks.json + * + * @param source - Source directory containing the project root + * @param contractName - Name of the contract to deploy (e.g., "AcrossFacet") + * @param network - Target network name (e.g., "arbitrum", "mainnet") + * @param privateKey - Private key secret for deployment + * @param environment - Deployment environment ("staging" or "production", defaults to "production") + */ + @func() + async deployContractAdvanced( + source: Directory, + contractName: string, + network: string, + privateKey: Secret, + environment?: string + ): Promise { + const env = environment || 'production' + + // Read network configuration from networks.json + const networksFile = source.file('config/networks.json') + const networksContent = await networksFile.contents() + const networks = JSON.parse(networksContent) + + if (!networks[network]) { + throw new Error(`Network ${network} not found in networks.json`) + } + + const networkConfig = networks[network] as NetworkConfig + + // Build the project first + const builtContainer = this.buildProject(source) + + const scriptPath = `script/deploy/facets/Deploy${contractName}.s.sol` + + // Generate deployment salt - use container to generate hash since we need the built artifacts + const saltContainer = builtContainer.withExec([ 'sh', '-c', - `test -f .env && source .env; ${forgeArgs.join(' ')}`, - ] + ` + # Generate deployment salt from bytecode + if [ "${contractName}" = "LiFiDiamondImmutable" ]; then + echo "0xc726deb4bf42c6ef5d0b4e3080ace43aed9b270938861f7cacf900eba890fa66" + else + BYTECODE=$(cat /workspace/out/${contractName}.sol/${contractName}.json | grep '"bytecode"' | cut -d'"' -f4) + echo "0x$(echo -n "$BYTECODE" | sha256sum | cut -d' ' -f1)" + fi + `, + ]) + + const deploySalt = await saltContainer.stdout() + + // Execute deployment + const deploymentContainer = this.deployContract( + source, + scriptPath, + network, + deploySalt.trim(), + networkConfig.create3Factory, + env === 'production' ? '' : 'staging.', + privateKey, // privateKey passed as secret + networkConfig.deployedWithSolcVersion, + networkConfig.deployedWithEvmVersion, + '130', // gasEstimateMultiplier + true, // broadcast + true, // legacy + true, // slow + false, // skipSimulation + undefined, // verbosity - omit by default + undefined, // defaultDiamondAddressDeploysalt + 'true', // deployToDefaultDiamondAddress + undefined // diamondType + ) + + // Extract deployment results from the output + const deploymentOutput = await deploymentContainer.stdout() - // Execute the forge script command with .env sourcing - return container.withExec(forgeCommand) + // Parse the JSON output to extract contract address and constructor args + let contractAddress = '' + let constructorArgs = '0x' + + try { + const result = JSON.parse(deploymentOutput) + if (result.returns && result.returns.deployed) { + contractAddress = result.returns.deployed.value + } + if (result.returns && result.returns.constructorArgs) { + constructorArgs = result.returns.constructorArgs.value + } + } catch (e) { + // If parsing fails, try to extract address from logs + const addressMatch = deploymentOutput.match(/0x[a-fA-F0-9]{40}/) + if (addressMatch) { + contractAddress = addressMatch[0] + } + } + + if (!contractAddress) { + throw new Error( + 'Failed to extract contract address from deployment output' + ) + } + + // Update deployment logs + const loggedContainer = await this.logDeployment( + deploymentContainer, + contractName, + network, + env, + contractAddress, + constructorArgs, + deploySalt.trim() + ) + + // Attempt contract verification + const finalContainer = await this.attemptVerification( + loggedContainer, + source, + contractName, + contractAddress, + constructorArgs, + networkConfig, + env + ) + + return finalContainer + } + + /** + * Log deployment details to deployment files + */ + private async logDeployment( + container: Container, + contractName: string, + network: string, + environment: string, + contractAddress: string, + constructorArgs: string, + deploySalt: string + ): Promise { + const fileSuffix = environment === 'production' ? '' : '.staging' + const deploymentFile = `deployments/${network}${fileSuffix}.json` + const logFile = 'deployments/_deployments_log_file.json' + + // Read current deployment files or create empty ones + const readDeploymentFile = container.withExec([ + 'sh', + '-c', + ` + if [ -f "/workspace/${deploymentFile}" ]; then + cat "/workspace/${deploymentFile}" + else + echo '{}' + fi + `, + ]) + + const currentDeploymentsRaw = await readDeploymentFile.stdout() + + const readLogFile = container.withExec([ + 'sh', + '-c', + ` + if [ -f "/workspace/${logFile}" ]; then + cat "/workspace/${logFile}" + else + echo '[]' + fi + `, + ]) + + const currentLogsRaw = await readLogFile.stdout() + + // Parse and update deployment data using TypeScript + const timestamp = new Date().toISOString() + + let currentDeployments: any = {} + try { + currentDeployments = JSON.parse(currentDeploymentsRaw.trim() || '{}') + } catch (e) { + currentDeployments = {} + } + + let currentLogs: any[] = [] + try { + currentLogs = JSON.parse(currentLogsRaw.trim() || '[]') + } catch (e) { + currentLogs = [] + } + + // Update deployment data + currentDeployments[contractName] = { + address: contractAddress, + constructorArgs: constructorArgs, + deploySalt: deploySalt, + timestamp: timestamp, + verified: false, + } + + // Add to master log (remove existing entry for same contract/network/environment if exists) + currentLogs = currentLogs.filter( + (log) => + !( + log.contractName === contractName && + log.network === network && + log.environment === environment + ) + ) + + currentLogs.push({ + contractName: contractName, + network: network, + environment: environment, + address: contractAddress, + constructorArgs: constructorArgs, + deploySalt: deploySalt, + timestamp: timestamp, + verified: false, + }) + + // Write updated files + const updatedDeployments = JSON.stringify(currentDeployments, null, 2) + const updatedLogs = JSON.stringify(currentLogs, null, 2) + + const writeDeploymentFile = container.withNewFile( + `/workspace/${deploymentFile}`, + updatedDeployments + ) + + const writeLogFile = writeDeploymentFile.withNewFile( + `/workspace/${logFile}`, + updatedLogs + ) + + return writeLogFile + } + + /** + * Attempt contract verification + */ + private async attemptVerification( + container: Container, + source: Directory, + contractName: string, + contractAddress: string, + constructorArgs: string, + networkConfig: NetworkConfig, + environment: string + ): Promise { + try { + // Determine chain ID from network config + const chainId = networkConfig.chainId.toString() + + // Attempt verification using the existing verifyContract function + const verificationContainer = this.verifyContract( + source, + contractName, + contractAddress, + constructorArgs, + chainId, + undefined, // auto-detect contract file path + undefined, // API key should be determined from network config + networkConfig.verificationType || 'etherscan', + networkConfig.deployedWithSolcVersion, + networkConfig.deployedWithEvmVersion, + true, // watch + true // skipIsVerifiedCheck + ) + + // Execute verification and update logs on success + const verifiedContainer = verificationContainer.withExec([ + 'sh', + '-c', + ` + echo "Contract verification completed successfully" + # Update deployment logs to mark as verified + DEPLOYMENT_FILE="deployments/${networkConfig.chainId}${ + environment === 'production' ? '' : '.staging' + }.json" + if [ -f "/workspace/$DEPLOYMENT_FILE" ]; then + # Use sed to update the verified field since we don't have jq + sed -i 's/"verified": false/"verified": true/g' "/workspace/$DEPLOYMENT_FILE" + fi + `, + ]) + + return verifiedContainer + } catch (error) { + // If verification fails, continue with unverified deployment + console.warn(`Contract verification failed: ${error}`) + return container.withExec([ + 'echo', + `Warning: Contract verification failed for ${contractName} at ${contractAddress}`, + ]) + } } } diff --git a/.dagger/yarn.lock b/.dagger/yarn.lock deleted file mode 100644 index ab49665af..000000000 --- a/.dagger/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -typescript@^5.5.4: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== From 9841cf94a7b6bc51c4ee0a9403f37f1ee44624d8 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Fri, 4 Jul 2025 18:41:54 +0300 Subject: [PATCH 03/37] update --- .dagger/src/index.ts | 332 ++++++++++++------------------------------- 1 file changed, 93 insertions(+), 239 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 5a9fe1801..570ba5a1c 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -60,6 +60,7 @@ export class LifiContracts { '/workspace/broadcast', ]) .withUser('foundry') + .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') .withExec(['forge', 'build']) return container @@ -156,6 +157,7 @@ export class LifiContracts { // Set required environment variables that the deployment scripts expect let deployContainer = containerWithDeployments + .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') .withEnvVariable('DEPLOYSALT', deploySalt) .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) .withEnvVariable('NETWORK', network) @@ -186,12 +188,12 @@ export class LifiContracts { } /** - * Verify a smart contract using Foundry forge verify-contract + * Verify a smart contract using an existing built container * - * This function provides containerized contract verification using the same - * built environment as deployment. It can be used as an alternative to the - * traditional verifyContract bash function. + * This function reuses a pre-built container to avoid rebuilding and ensure + * the same artifacts are used for both deployment and verification. * + * @param builtContainer - Pre-built container with compiled artifacts * @param source - Source directory containing the project root * @param contractName - Name of the contract to verify (e.g., "Executor") * @param contractAddress - Deployed contract address @@ -206,7 +208,8 @@ export class LifiContracts { * @param skipIsVerifiedCheck - Whether to skip already verified check (default: true) */ @func() - verifyContract( + verifyContractWithBuiltContainer( + builtContainer: Container, source: Directory, contractName: string, contractAddress: string, @@ -224,40 +227,9 @@ export class LifiContracts { const verificationService = verifier || 'etherscan' const shouldWatch = watch !== false const shouldSkipVerifiedCheck = skipIsVerifiedCheck !== false - const solc = solcVersion || '0.8.29' - const evm = evmVersion || 'cancun' - // Build the project first using the same parameters as deployment - let container = dag - .container() - .from('ghcr.io/foundry-rs/foundry:latest') - .withDirectory('/workspace/src', source.directory('src')) - .withDirectory('/workspace/lib', source.directory('lib')) - .withDirectory('/workspace/script', source.directory('script')) - .withFile('/workspace/foundry.toml', source.file('foundry.toml')) - .withFile('/workspace/remappings.txt', source.file('remappings.txt')) - .withFile('/workspace/.env', source.file('.env')) - .withWorkdir('/workspace') - .withUser('root') - .withExec([ - 'mkdir', - '-p', - '/workspace/out', - '/workspace/cache', - '/workspace/broadcast', - ]) - .withExec([ - 'chown', - 'foundry:foundry', - '/workspace/out', - '/workspace/cache', - '/workspace/broadcast', - ]) - .withUser('foundry') - .withExec(['forge', 'build', '--use', solc, '--evm-version', evm]) - - // Mount the deployments directory - const builtContainer = container.withMountedDirectory( + // Mount the deployments directory to the built container + const containerWithDeployments = builtContainer.withMountedDirectory( '/workspace/deployments', source.directory('deployments') ) @@ -320,180 +292,27 @@ export class LifiContracts { forgeArgs.push('--verifier', verificationService) } - // Set up container with environment variables - let verifyContainer = builtContainer + // Add optimizer settings to match deployment + forgeArgs.push('--optimizer-runs', '1000000') // Add API key if provided and not using sourcify if (apiKey && verificationService !== 'sourcify') { - verifyContainer = verifyContainer.withEnvVariable( - 'ETHERSCAN_API_KEY', - apiKey - ) - forgeArgs.push('-e', 'ETHERSCAN_API_KEY') + forgeArgs.push('-e', apiKey) + } else if (verificationService === 'etherscan') { + // For etherscan verification without explicit API key, get MAINNET_ETHERSCAN_API_KEY from environment + // and pass the actual value directly to the verification command + const mainnetApiKey = process.env.MAINNET_ETHERSCAN_API_KEY + if (mainnetApiKey) { + forgeArgs.push('-e', mainnetApiKey) + } else { + console.warn( + 'MAINNET_ETHERSCAN_API_KEY not found in environment, verification may fail' + ) + } } // Execute the verification command - return verifyContainer.withExec(forgeArgs) - } - - /** - * Verify a smart contract with simplified interface matching bash script - * - * This function provides a simplified interface that matches the existing - * verifyContract bash function signature for easier integration. - * - * @param source - Source directory containing the project root - * @param network - Target network name (e.g., "mainnet", "polygon", "arbitrum") - * @param contractName - Name of the contract to verify (e.g., "Executor") - * @param contractAddress - Deployed contract address - * @param constructorArgs - Constructor arguments in hex format (e.g., "0x123...") - * @param solcVersion - Solidity compiler version (e.g., "0.8.29") - * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") - */ - @func() - verifyContractSimple( - source: Directory, - network: string, - contractName: string, - contractAddress: string, - constructorArgs: string, - solcVersion?: string, - evmVersion?: string - ): Container { - const solc = solcVersion || '0.8.29' - const evm = evmVersion || 'cancun' - - // Create a mapping of network names to chain IDs - // This should ideally read from networks.json, but for now we'll use a static mapping - const networkToChainId: { [key: string]: string } = { - mainnet: '1', - arbitrum: '42161', - polygon: '137', - optimism: '10', - base: '8453', - avalanche: '43114', - bsc: '56', - fantom: '250', - gnosis: '100', - celo: '42220', - moonbeam: '1284', - moonriver: '1285', - aurora: '1313161554', - cronos: '25', - fuse: '122', - metis: '1088', - boba: '288', - viction: '88', - mantle: '5000', - linea: '59144', - scroll: '534352', - zksync: '324', - polygonzkevm: '1101', - mode: '34443', - blast: '81457', - fraxtal: '252', - sei: '1329', - taiko: '167000', - worldchain: '480', - sonic: '146', - unichain: '130', - abstract: '2741', - apechain: '33139', - berachain: '80094', - bob: '60808', - corn: '21000000', - etherlink: '42793', - flare: '14', - gravity: '1625', - hyperevm: '999', - immutablezkevm: '13371', - ink: '57073', - kaia: '8217', - katana: '747474', - lens: '232', - lisk: '1135', - nibiru: '6900', - opbnb: '204', - plume: '98866', - rootstock: '30', - soneium: '1868', - superposition: '55244', - swellchain: '1923', - vana: '1480', - xdc: '50', - xlayer: '196', - } - - const chainId = networkToChainId[network] || '1' // default to mainnet if network not found - - return this.verifyContract( - source, - contractName, - contractAddress, - constructorArgs, - chainId, - undefined, // auto-detect contract file path - undefined, // API key should be determined from network config - 'etherscan', // default verifier - solc, - evm - ) - } - /** - * Deploy a smart contract with simplified interface matching bash script - * - * This function provides a simplified interface that matches the existing - * forge script execution in the bash deployment scripts. - * - * @param source - Source directory containing the project root - * @param scriptPath - Path to the deployment script - * @param network - Target network name - * @param environment - Environment (staging/production) - * @param privateKey - Private key secret for deployment - * @param deploySalt - Salt for CREATE3 deployment - * @param create3FactoryAddress - Address of the CREATE3 factory contract - * @param gasEstimateMultiplier - Gas estimate multiplier (optional) - * @param defaultDiamondAddressDeploysalt - Default diamond address deploy salt (optional) - * @param deployToDefaultDiamondAddress - Whether to deploy to default diamond address (optional) - * @param diamondType - Diamond type for CelerIMFacet (optional) - */ - @func() - deployContractSimple( - source: Directory, - scriptPath: string, - network: string, - environment: string, - privateKey: Secret, - deploySalt: string, - create3FactoryAddress: string, - gasEstimateMultiplier?: string, - defaultDiamondAddressDeploysalt?: string, - deployToDefaultDiamondAddress?: string, - diamondType?: string - ): Container { - // Determine file suffix based on environment - const fileSuffix = environment === 'production' ? '' : 'staging.' - - return this.deployContract( - source, - scriptPath, - network, - deploySalt, - create3FactoryAddress, - fileSuffix, - privateKey, - '0.8.29', // default solc version - 'cancun', // default evm version - gasEstimateMultiplier, - true, // broadcast - true, // legacy - true, // slow - false, // skipSimulation - undefined, // verbosity - omit by default - defaultDiamondAddressDeploysalt, - deployToDefaultDiamondAddress, - diamondType - ) + return containerWithDeployments.withExec(forgeArgs) } /** @@ -610,7 +429,7 @@ export class LifiContracts { deploySalt.trim() ) - // Attempt contract verification + // Attempt contract verification using the same built container const finalContainer = await this.attemptVerification( loggedContainer, source, @@ -618,7 +437,9 @@ export class LifiContracts { contractAddress, constructorArgs, networkConfig, - env + network, + env, + builtContainer // Pass the built container to reuse artifacts ) return finalContainer @@ -662,7 +483,7 @@ export class LifiContracts { if [ -f "/workspace/${logFile}" ]; then cat "/workspace/${logFile}" else - echo '[]' + echo '{}' fi `, ]) @@ -670,7 +491,10 @@ export class LifiContracts { const currentLogsRaw = await readLogFile.stdout() // Parse and update deployment data using TypeScript - const timestamp = new Date().toISOString() + const timestamp = new Date() + .toISOString() + .replace('T', ' ') + .replace(/\.\d{3}Z$/, '') let currentDeployments: any = {} try { @@ -679,14 +503,14 @@ export class LifiContracts { currentDeployments = {} } - let currentLogs: any[] = [] + let currentLogs: any = {} try { - currentLogs = JSON.parse(currentLogsRaw.trim() || '[]') + currentLogs = JSON.parse(currentLogsRaw.trim() || '{}') } catch (e) { - currentLogs = [] + currentLogs = {} } - // Update deployment data + // Update deployment data (network-specific file) currentDeployments[contractName] = { address: contractAddress, constructorArgs: constructorArgs, @@ -695,25 +519,38 @@ export class LifiContracts { verified: false, } - // Add to master log (remove existing entry for same contract/network/environment if exists) - currentLogs = currentLogs.filter( - (log) => - !( - log.contractName === contractName && - log.network === network && - log.environment === environment - ) + // Update master log with nested structure: contractName -> network -> environment -> version -> array + if (!currentLogs[contractName]) { + currentLogs[contractName] = {} + } + if (!currentLogs[contractName][network]) { + currentLogs[contractName][network] = {} + } + if (!currentLogs[contractName][network][environment]) { + currentLogs[contractName][network][environment] = {} + } + + // Use version 1.0.0 as default (could be extracted from contract source later) + const version = '1.0.0' + if (!currentLogs[contractName][network][environment][version]) { + currentLogs[contractName][network][environment][version] = [] + } + + // Remove existing entry for same address if it exists + currentLogs[contractName][network][environment][version] = currentLogs[ + contractName + ][network][environment][version].filter( + (entry: any) => entry.ADDRESS !== contractAddress ) - currentLogs.push({ - contractName: contractName, - network: network, - environment: environment, - address: contractAddress, - constructorArgs: constructorArgs, - deploySalt: deploySalt, - timestamp: timestamp, - verified: false, + // Add new deployment entry + currentLogs[contractName][network][environment][version].push({ + ADDRESS: contractAddress, + OPTIMIZER_RUNS: '1000000', // Default value, could be extracted from build info + TIMESTAMP: timestamp, + CONSTRUCTOR_ARGS: constructorArgs, + SALT: deploySalt, + VERIFIED: 'false', }) // Write updated files @@ -743,14 +580,17 @@ export class LifiContracts { contractAddress: string, constructorArgs: string, networkConfig: NetworkConfig, - environment: string + network: string, + environment: string, + builtContainer: Container ): Promise { try { // Determine chain ID from network config const chainId = networkConfig.chainId.toString() - // Attempt verification using the existing verifyContract function - const verificationContainer = this.verifyContract( + // Use the built container for verification to reuse compiled artifacts + const verificationContainer = this.verifyContractWithBuiltContainer( + builtContainer, source, contractName, contractAddress, @@ -765,19 +605,33 @@ export class LifiContracts { true // skipIsVerifiedCheck ) + // Use the network name passed to the function + const fileSuffix = environment === 'production' ? '' : '.staging' + // Execute verification and update logs on success const verifiedContainer = verificationContainer.withExec([ 'sh', '-c', ` - echo "Contract verification completed successfully" + echo "Contract verification completed successfully for ${contractName} at ${contractAddress}" + echo "Using compiler: ${networkConfig.deployedWithSolcVersion}, EVM: ${networkConfig.deployedWithEvmVersion}" + echo "Constructor args: ${constructorArgs}" + # Update deployment logs to mark as verified - DEPLOYMENT_FILE="deployments/${networkConfig.chainId}${ - environment === 'production' ? '' : '.staging' - }.json" + DEPLOYMENT_FILE="deployments/${network}${fileSuffix}.json" if [ -f "/workspace/$DEPLOYMENT_FILE" ]; then - # Use sed to update the verified field since we don't have jq + # Use sed to update the verified field in network-specific file sed -i 's/"verified": false/"verified": true/g' "/workspace/$DEPLOYMENT_FILE" + echo "Updated network deployment file: $DEPLOYMENT_FILE" + fi + + # Update master log file - this is more complex due to nested structure + # We'll use sed to find and replace the VERIFIED field for this specific contract/network/environment + MASTER_LOG_FILE="deployments/_deployments_log_file.json" + if [ -f "/workspace/$MASTER_LOG_FILE" ]; then + # Replace "VERIFIED": "false" with "VERIFIED": "true" for entries with matching ADDRESS + sed -i 's/"VERIFIED": "false"/"VERIFIED": "true"/g' "/workspace/$MASTER_LOG_FILE" + echo "Updated master log file: $MASTER_LOG_FILE" fi `, ]) From f31fb9a32b9a9c8fb60d9a33b87842df76922906 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sat, 5 Jul 2025 18:01:01 +0300 Subject: [PATCH 04/37] rename --- .dagger/src/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 570ba5a1c..aa4eaf49e 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -67,7 +67,7 @@ export class LifiContracts { } /** - * Deploy a smart contract using Foundry forge script + * Deploy a smart contract using Foundry forge script (internal function) * * @param source - Source directory containing the project root * @param scriptPath - Path to the deployment script (e.g., "script/deploy/facets/DeployExecutor.s.sol") @@ -89,7 +89,7 @@ export class LifiContracts { * @param diamondType - Diamond type for CelerIMFacet (optional) */ @func() - deployContract( + deployContractInternal( source: Directory, scriptPath: string, network: string, @@ -208,7 +208,7 @@ export class LifiContracts { * @param skipIsVerifiedCheck - Whether to skip already verified check (default: true) */ @func() - verifyContractWithBuiltContainer( + verifyContract( builtContainer: Container, source: Directory, contractName: string, @@ -316,7 +316,7 @@ export class LifiContracts { } /** - * Deploy a smart contract with advanced configuration reading from networks.json + * Deploy a smart contract with configuration reading from networks.json * * @param source - Source directory containing the project root * @param contractName - Name of the contract to deploy (e.g., "AcrossFacet") @@ -325,7 +325,7 @@ export class LifiContracts { * @param environment - Deployment environment ("staging" or "production", defaults to "production") */ @func() - async deployContractAdvanced( + async deployContract( source: Directory, contractName: string, network: string, @@ -368,7 +368,7 @@ export class LifiContracts { const deploySalt = await saltContainer.stdout() // Execute deployment - const deploymentContainer = this.deployContract( + const deploymentContainer = this.deployContractInternal( source, scriptPath, network, @@ -589,7 +589,7 @@ export class LifiContracts { const chainId = networkConfig.chainId.toString() // Use the built container for verification to reuse compiled artifacts - const verificationContainer = this.verifyContractWithBuiltContainer( + const verificationContainer = this.verifyContract( builtContainer, source, contractName, From 164bb1985dbf7da4fa2322fdf4af205df2a2e37a Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sat, 5 Jul 2025 18:30:17 +0300 Subject: [PATCH 05/37] refactor --- .dagger/src/index.ts | 66 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index aa4eaf49e..a17edfd44 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -188,7 +188,7 @@ export class LifiContracts { } /** - * Verify a smart contract using an existing built container + * Verify a smart contract using an existing built container (internal function) * * This function reuses a pre-built container to avoid rebuilding and ensure * the same artifacts are used for both deployment and verification. @@ -208,7 +208,7 @@ export class LifiContracts { * @param skipIsVerifiedCheck - Whether to skip already verified check (default: true) */ @func() - verifyContract( + verifyContractInternal( builtContainer: Container, source: Directory, contractName: string, @@ -315,6 +315,66 @@ export class LifiContracts { return containerWithDeployments.withExec(forgeArgs) } + /** + * Verify a smart contract with configuration reading from networks.json + * + * This function builds the project and verifies a deployed contract using the same + * configuration as deployContract to ensure consistency. + * + * @param source - Source directory containing the project root + * @param contractName - Name of the contract to verify (e.g., "AcrossFacet") + * @param contractAddress - Deployed contract address + * @param constructorArgs - Constructor arguments in hex format (e.g., "0x123...") + * @param network - Target network name (e.g., "arbitrum", "mainnet") + * @param contractFilePath - Custom contract file path (optional, auto-detected if not provided) + * @param apiKey - API key for verification service (optional) + * @param watch - Whether to watch verification status (default: true) + * @param skipIsVerifiedCheck - Whether to skip already verified check (default: true) + */ + @func() + async verifyContract( + source: Directory, + contractName: string, + contractAddress: string, + constructorArgs: string, + network: string, + contractFilePath?: string, + apiKey?: string, + watch?: boolean, + skipIsVerifiedCheck?: boolean + ): Promise { + // Read network configuration from networks.json + const networksFile = source.file('config/networks.json') + const networksContent = await networksFile.contents() + const networks = JSON.parse(networksContent) + + if (!networks[network]) { + throw new Error(`Network ${network} not found in networks.json`) + } + + const networkConfig = networks[network] as NetworkConfig + + // Build the project first to get the same artifacts as deployment + const builtContainer = this.buildProject(source) + + // Use the built container for verification + return this.verifyContractInternal( + builtContainer, + source, + contractName, + contractAddress, + constructorArgs, + networkConfig.chainId.toString(), + contractFilePath, + apiKey, + networkConfig.verificationType || 'etherscan', + networkConfig.deployedWithSolcVersion, + networkConfig.deployedWithEvmVersion, + watch, + skipIsVerifiedCheck + ) + } + /** * Deploy a smart contract with configuration reading from networks.json * @@ -589,7 +649,7 @@ export class LifiContracts { const chainId = networkConfig.chainId.toString() // Use the built container for verification to reuse compiled artifacts - const verificationContainer = this.verifyContract( + const verificationContainer = this.verifyContractInternal( builtContainer, source, contractName, From f3317ef0b41b10135bb2666ba18fe652e07eaea0 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sun, 6 Jul 2025 20:36:40 +0300 Subject: [PATCH 06/37] remove bash code --- .dagger/src/index.ts | 68 ++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index a17edfd44..7ae4efc19 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -25,6 +25,49 @@ interface NetworkConfig { @object() export class LifiContracts { + /** + * Generate deployment salt from contract bytecode using TypeScript + * + * @param builtContainer - Pre-built container with compiled artifacts + * @param contractName - Name of the contract to generate salt for + */ + @func() + async generateDeploymentSalt( + builtContainer: Container, + contractName: string + ): Promise { + // Special case for LiFiDiamondImmutable - use hardcoded salt + if (contractName === 'LiFiDiamondImmutable') { + return '0xc726deb4bf42c6ef5d0b4e3080ace43aed9b270938861f7cacf900eba890fa66' + } + + // Read the bytecode from the compiled artifacts + const artifactPath = `/workspace/out/${contractName}.sol/${contractName}.json` + const artifactContainer = builtContainer.withExec(['cat', artifactPath]) + + const artifactContent = await artifactContainer.stdout() + + try { + const artifact = JSON.parse(artifactContent) + const bytecode = artifact.bytecode?.object || artifact.bytecode + + if (!bytecode) { + throw new Error(`No bytecode found for contract ${contractName}`) + } + + // Generate SHA256 hash of the bytecode using container's sha256sum + const hashContainer = builtContainer.withExec([ + 'sh', + '-c', + `echo -n "${bytecode}" | sha256sum | cut -d' ' -f1`, + ]) + + const hash = await hashContainer.stdout() + return `0x${hash.trim()}` + } catch (error) { + throw new Error(`Failed to generate salt for ${contractName}: ${error}`) + } + } /** * Build the Foundry project using forge build * @@ -410,29 +453,18 @@ export class LifiContracts { const scriptPath = `script/deploy/facets/Deploy${contractName}.s.sol` - // Generate deployment salt - use container to generate hash since we need the built artifacts - const saltContainer = builtContainer.withExec([ - 'sh', - '-c', - ` - # Generate deployment salt from bytecode - if [ "${contractName}" = "LiFiDiamondImmutable" ]; then - echo "0xc726deb4bf42c6ef5d0b4e3080ace43aed9b270938861f7cacf900eba890fa66" - else - BYTECODE=$(cat /workspace/out/${contractName}.sol/${contractName}.json | grep '"bytecode"' | cut -d'"' -f4) - echo "0x$(echo -n "$BYTECODE" | sha256sum | cut -d' ' -f1)" - fi - `, - ]) - - const deploySalt = await saltContainer.stdout() + // Generate deployment salt using TypeScript method + const deploySalt = await this.generateDeploymentSalt( + builtContainer, + contractName + ) // Execute deployment const deploymentContainer = this.deployContractInternal( source, scriptPath, network, - deploySalt.trim(), + deploySalt, networkConfig.create3Factory, env === 'production' ? '' : 'staging.', privateKey, // privateKey passed as secret @@ -486,7 +518,7 @@ export class LifiContracts { env, contractAddress, constructorArgs, - deploySalt.trim() + deploySalt ) // Attempt contract verification using the same built container From fcac8ee69e18d6604aa35ea9de243db2af3be825 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sun, 6 Jul 2025 20:49:13 +0300 Subject: [PATCH 07/37] remove bash code --- .dagger/src/index.ts | 150 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 128 insertions(+), 22 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 7ae4efc19..71bd09e95 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -537,6 +537,123 @@ export class LifiContracts { return finalContainer } + /** + * Update deployment logs to mark contract as verified using TypeScript + * + * @param container - Container to execute the update in + * @param contractName - Name of the verified contract + * @param network - Target network name + * @param environment - Deployment environment + * @param contractAddress - Address of the verified contract + */ + @func() + async updateVerificationLogs( + container: Container, + contractName: string, + network: string, + environment: string, + contractAddress: string + ): Promise { + const fileSuffix = environment === 'production' ? '' : '.staging' + const deploymentFile = `deployments/${network}${fileSuffix}.json` + const logFile = 'deployments/_deployments_log_file.json' + + // Read current deployment files + const readDeploymentFile = container.withExec([ + 'sh', + '-c', + ` + if [ -f "/workspace/${deploymentFile}" ]; then + cat "/workspace/${deploymentFile}" + else + echo '{}' + fi + `, + ]) + + const currentDeploymentsRaw = await readDeploymentFile.stdout() + + const readLogFile = container.withExec([ + 'sh', + '-c', + ` + if [ -f "/workspace/${logFile}" ]; then + cat "/workspace/${logFile}" + else + echo '{}' + fi + `, + ]) + + const currentLogsRaw = await readLogFile.stdout() + + // Parse and update deployment data using TypeScript + let currentDeployments: any = {} + try { + currentDeployments = JSON.parse(currentDeploymentsRaw.trim() || '{}') + } catch (e) { + currentDeployments = {} + } + + let currentLogs: any = {} + try { + currentLogs = JSON.parse(currentLogsRaw.trim() || '{}') + } catch (e) { + currentLogs = {} + } + + // Update network-specific deployment file + if (currentDeployments[contractName]) { + currentDeployments[contractName].verified = true + } + + // Update master log file - find entries with matching address and mark as verified + if ( + currentLogs[contractName] && + currentLogs[contractName][network] && + currentLogs[contractName][network][environment] + ) { + Object.keys(currentLogs[contractName][network][environment]).forEach( + (version) => { + const entries = + currentLogs[contractName][network][environment][version] + if (Array.isArray(entries)) { + entries.forEach((entry: any) => { + if (entry.ADDRESS === contractAddress) { + entry.VERIFIED = 'true' + } + }) + } + } + ) + } + + // Write updated files + const updatedDeployments = JSON.stringify(currentDeployments, null, 2) + const updatedLogs = JSON.stringify(currentLogs, null, 2) + + const writeDeploymentFile = container.withNewFile( + `/workspace/${deploymentFile}`, + updatedDeployments + ) + + const writeLogFile = writeDeploymentFile.withNewFile( + `/workspace/${logFile}`, + updatedLogs + ) + + // Log success message + return writeLogFile.withExec([ + 'sh', + '-c', + ` + echo "Contract verification completed successfully for ${contractName} at ${contractAddress}" + echo "Updated network deployment file: ${deploymentFile}" + echo "Updated master log file: ${logFile}" + `, + ]) + } + /** * Log deployment details to deployment files */ @@ -697,37 +814,26 @@ export class LifiContracts { true // skipIsVerifiedCheck ) - // Use the network name passed to the function - const fileSuffix = environment === 'production' ? '' : '.staging' - - // Execute verification and update logs on success - const verifiedContainer = verificationContainer.withExec([ + // Log verification details + const logContainer = verificationContainer.withExec([ 'sh', '-c', ` echo "Contract verification completed successfully for ${contractName} at ${contractAddress}" echo "Using compiler: ${networkConfig.deployedWithSolcVersion}, EVM: ${networkConfig.deployedWithEvmVersion}" echo "Constructor args: ${constructorArgs}" - - # Update deployment logs to mark as verified - DEPLOYMENT_FILE="deployments/${network}${fileSuffix}.json" - if [ -f "/workspace/$DEPLOYMENT_FILE" ]; then - # Use sed to update the verified field in network-specific file - sed -i 's/"verified": false/"verified": true/g' "/workspace/$DEPLOYMENT_FILE" - echo "Updated network deployment file: $DEPLOYMENT_FILE" - fi - - # Update master log file - this is more complex due to nested structure - # We'll use sed to find and replace the VERIFIED field for this specific contract/network/environment - MASTER_LOG_FILE="deployments/_deployments_log_file.json" - if [ -f "/workspace/$MASTER_LOG_FILE" ]; then - # Replace "VERIFIED": "false" with "VERIFIED": "true" for entries with matching ADDRESS - sed -i 's/"VERIFIED": "false"/"VERIFIED": "true"/g' "/workspace/$MASTER_LOG_FILE" - echo "Updated master log file: $MASTER_LOG_FILE" - fi `, ]) + // Update deployment logs using TypeScript method + const verifiedContainer = await this.updateVerificationLogs( + logContainer, + contractName, + network, + environment, + contractAddress + ) + return verifiedContainer } catch (error) { // If verification fails, continue with unverified deployment From 503d6d4ecd74bb8e05f65cca0e064d2fca716b3e Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sun, 6 Jul 2025 21:49:59 +0300 Subject: [PATCH 08/37] remove --- .dagger/src/index.ts | 128 +++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 71bd09e95..9ecdc4272 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -25,58 +25,19 @@ interface NetworkConfig { @object() export class LifiContracts { - /** - * Generate deployment salt from contract bytecode using TypeScript - * - * @param builtContainer - Pre-built container with compiled artifacts - * @param contractName - Name of the contract to generate salt for - */ - @func() - async generateDeploymentSalt( - builtContainer: Container, - contractName: string - ): Promise { - // Special case for LiFiDiamondImmutable - use hardcoded salt - if (contractName === 'LiFiDiamondImmutable') { - return '0xc726deb4bf42c6ef5d0b4e3080ace43aed9b270938861f7cacf900eba890fa66' - } - - // Read the bytecode from the compiled artifacts - const artifactPath = `/workspace/out/${contractName}.sol/${contractName}.json` - const artifactContainer = builtContainer.withExec(['cat', artifactPath]) - - const artifactContent = await artifactContainer.stdout() - - try { - const artifact = JSON.parse(artifactContent) - const bytecode = artifact.bytecode?.object || artifact.bytecode - - if (!bytecode) { - throw new Error(`No bytecode found for contract ${contractName}`) - } - - // Generate SHA256 hash of the bytecode using container's sha256sum - const hashContainer = builtContainer.withExec([ - 'sh', - '-c', - `echo -n "${bytecode}" | sha256sum | cut -d' ' -f1`, - ]) - - const hash = await hashContainer.stdout() - return `0x${hash.trim()}` - } catch (error) { - throw new Error(`Failed to generate salt for ${contractName}: ${error}`) - } - } /** * Build the Foundry project using forge build * * @param source - Source directory containing the project root - * @param uid - User ID to match host user (optional) - * @param gid - Group ID to match host group (optional) + * @param solcVersion - Solidity compiler version (e.g., "0.8.29") + * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") */ @func() - buildProject(source: Directory): Container { + buildProject( + source: Directory, + solcVersion?: string, + evmVersion?: string + ): Container { let container = dag .container() .from('ghcr.io/foundry-rs/foundry:latest') @@ -104,7 +65,19 @@ export class LifiContracts { ]) .withUser('foundry') .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') - .withExec(['forge', 'build']) + + // Build forge build command with version parameters + const buildArgs = ['forge', 'build'] + + if (solcVersion) { + buildArgs.push('--use', solcVersion) + } + + if (evmVersion) { + buildArgs.push('--evm-version', evmVersion) + } + + container = container.withExec(buildArgs) return container } @@ -161,8 +134,8 @@ export class LifiContracts { const solc = solcVersion || '0.8.29' const evm = evmVersion || 'cancun' - // Build the project first - const builtContainer = this.buildProject(source) + // Build the project first with the same versions as deployment + const builtContainer = this.buildProject(source, solc, evm) // Mount the deployments directory to the built container const containerWithDeployments = builtContainer.withMountedDirectory( @@ -272,7 +245,9 @@ export class LifiContracts { const shouldSkipVerifiedCheck = skipIsVerifiedCheck !== false // Mount the deployments directory to the built container - const containerWithDeployments = builtContainer.withMountedDirectory( + // Note: The built container already has src, lib, script, foundry.toml, etc. + // We just need to mount the deployments directory for verification + builtContainer = builtContainer.withMountedDirectory( '/workspace/deployments', source.directory('deployments') ) @@ -307,7 +282,7 @@ export class LifiContracts { } // Build base verification command - const forgeArgs = ['forge', 'verify-contract'] + const forgeArgs = ['forge', 'verify-contract', '--root', '/workspace'] // Add watch flag if (shouldWatch) { @@ -317,8 +292,8 @@ export class LifiContracts { // Add chain ID forgeArgs.push('--chain', chainId) - // Add contract address and path - forgeArgs.push(contractAddress, finalContractFilePath) + // Add contract address and contract name + forgeArgs.push(contractAddress, contractName) // Add skip verification check flag if (shouldSkipVerifiedCheck) { @@ -335,9 +310,6 @@ export class LifiContracts { forgeArgs.push('--verifier', verificationService) } - // Add optimizer settings to match deployment - forgeArgs.push('--optimizer-runs', '1000000') - // Add API key if provided and not using sourcify if (apiKey && verificationService !== 'sourcify') { forgeArgs.push('-e', apiKey) @@ -355,7 +327,7 @@ export class LifiContracts { } // Execute the verification command - return containerWithDeployments.withExec(forgeArgs) + return builtContainer.withExec(forgeArgs) } /** @@ -398,7 +370,11 @@ export class LifiContracts { const networkConfig = networks[network] as NetworkConfig // Build the project first to get the same artifacts as deployment - const builtContainer = this.buildProject(source) + const builtContainer = this.buildProject( + source, + networkConfig.deployedWithSolcVersion, + networkConfig.deployedWithEvmVersion + ) // Use the built container for verification return this.verifyContractInternal( @@ -448,16 +424,38 @@ export class LifiContracts { const networkConfig = networks[network] as NetworkConfig - // Build the project first - const builtContainer = this.buildProject(source) + // Build the project first with network-specific versions + const builtContainer = this.buildProject( + source, + networkConfig.deployedWithSolcVersion, + networkConfig.deployedWithEvmVersion + ) const scriptPath = `script/deploy/facets/Deploy${contractName}.s.sol` - // Generate deployment salt using TypeScript method - const deploySalt = await this.generateDeploymentSalt( - builtContainer, - contractName - ) + // Generate deployment salt + let deploySalt: string + // Read the bytecode from compiled artifacts + const artifactPath = `/workspace/out/${contractName}.sol/${contractName}.json` + const artifactContainer = builtContainer.withExec(['cat', artifactPath]) + const artifactContent = await artifactContainer.stdout() + + const artifact = JSON.parse(artifactContent) + const bytecode = artifact.bytecode?.object || artifact.bytecode + + if (!bytecode) { + throw new Error(`No bytecode found for contract ${contractName}`) + } + + // Generate SHA256 hash of the bytecode + const hashContainer = builtContainer.withExec([ + 'sh', + '-c', + `echo -n "${bytecode}" | sha256sum | cut -d' ' -f1`, + ]) + + const hash = await hashContainer.stdout() + deploySalt = `0x${hash.trim()}` // Execute deployment const deploymentContainer = this.deployContractInternal( From be9513f3a153562ac3671bd2c4a84cfa09facfde Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Sun, 6 Jul 2025 22:10:34 +0300 Subject: [PATCH 09/37] cleanup --- .dagger/src/index.ts | 51 +++++--------------------------------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 9ecdc4272..8297997b3 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -231,11 +231,8 @@ export class LifiContracts { contractAddress: string, constructorArgs: string, chainId: string, - contractFilePath?: string, apiKey?: string, verifier?: string, - solcVersion?: string, - evmVersion?: string, watch?: boolean, skipIsVerifiedCheck?: boolean ): Container { @@ -252,37 +249,8 @@ export class LifiContracts { source.directory('deployments') ) - // Determine contract file path - use provided path or auto-detect - let finalContractFilePath: string - - if (contractFilePath) { - finalContractFilePath = contractFilePath - } else { - // Auto-detect based on contract name - follows pattern from getContractFilePath - // The helper function searches in src/ directory, so we need to construct the full path - // Common locations: src/Facets/, src/Periphery/, src/ - finalContractFilePath = `src/Facets/${contractName}.sol:${contractName}` - - // For some contracts like LiFiDiamond, they're directly in src/ - if ( - contractName === 'LiFiDiamond' || - contractName === 'LiFiDiamondImmutable' - ) { - finalContractFilePath = `src/${contractName}.sol:${contractName}` - } - // Periphery contracts are in src/Periphery/ - else if ( - contractName.includes('Receiver') || - contractName.includes('Executor') || - contractName.includes('FeeCollector') || - contractName.includes('ERC20Proxy') - ) { - finalContractFilePath = `src/Periphery/${contractName}.sol:${contractName}` - } - } - // Build base verification command - const forgeArgs = ['forge', 'verify-contract', '--root', '/workspace'] + const forgeArgs = ['forge', 'verify-contract'] // Add watch flag if (shouldWatch) { @@ -302,7 +270,7 @@ export class LifiContracts { // Add constructor args if present if (constructorArgs && constructorArgs !== '0x') { - forgeArgs.push('--constructor-args', constructorArgs) + forgeArgs.push('--constructor-args', constructorArgs.trim()) } // Add verifier @@ -353,7 +321,6 @@ export class LifiContracts { contractAddress: string, constructorArgs: string, network: string, - contractFilePath?: string, apiKey?: string, watch?: boolean, skipIsVerifiedCheck?: boolean @@ -384,11 +351,8 @@ export class LifiContracts { contractAddress, constructorArgs, networkConfig.chainId.toString(), - contractFilePath, apiKey, networkConfig.verificationType || 'etherscan', - networkConfig.deployedWithSolcVersion, - networkConfig.deployedWithEvmVersion, watch, skipIsVerifiedCheck ) @@ -528,8 +492,7 @@ export class LifiContracts { constructorArgs, networkConfig, network, - env, - builtContainer // Pass the built container to reuse artifacts + env ) return finalContainer @@ -788,8 +751,7 @@ export class LifiContracts { constructorArgs: string, networkConfig: NetworkConfig, network: string, - environment: string, - builtContainer: Container + environment: string ): Promise { try { // Determine chain ID from network config @@ -797,17 +759,14 @@ export class LifiContracts { // Use the built container for verification to reuse compiled artifacts const verificationContainer = this.verifyContractInternal( - builtContainer, + container, source, contractName, contractAddress, constructorArgs, chainId, undefined, // auto-detect contract file path - undefined, // API key should be determined from network config networkConfig.verificationType || 'etherscan', - networkConfig.deployedWithSolcVersion, - networkConfig.deployedWithEvmVersion, true, // watch true // skipIsVerifiedCheck ) From 89a71ace19a044da744976be241da87a6145123f Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 7 Jul 2025 18:27:53 +0300 Subject: [PATCH 10/37] export artifacts --- .dagger/src/index.ts | 202 +++++++++++-------------- deployments/_deployments_log_file.json | 10 +- deployments/arbitrum.json | 8 +- 3 files changed, 103 insertions(+), 117 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 8297997b3..7049bd5e5 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -12,6 +12,7 @@ import { object, func, } from '@dagger.io/dagger' +import * as fs from 'fs/promises' interface NetworkConfig { chainId: number @@ -159,7 +160,7 @@ export class LifiContracts { // Add verbosity flag only if specified if (verbosity) { - forgeArgs.splice(-1, 0, `-${verbosity}`) + forgeArgs.push(`-${verbosity}`) } // Add conditional flags @@ -215,11 +216,8 @@ export class LifiContracts { * @param contractAddress - Deployed contract address * @param constructorArgs - Constructor arguments in hex format (e.g., "0x123...") * @param chainId - Chain ID for verification - * @param contractFilePath - Custom contract file path (optional, auto-detected if not provided) * @param apiKey - API key for verification service (optional) * @param verifier - Verification service ("etherscan", "blockscout", "sourcify") (default: "etherscan") - * @param solcVersion - Solidity compiler version (e.g., "0.8.29") - * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") * @param watch - Whether to watch verification status (default: true) * @param skipIsVerifiedCheck - Whether to skip already verified check (default: true) */ @@ -281,17 +279,6 @@ export class LifiContracts { // Add API key if provided and not using sourcify if (apiKey && verificationService !== 'sourcify') { forgeArgs.push('-e', apiKey) - } else if (verificationService === 'etherscan') { - // For etherscan verification without explicit API key, get MAINNET_ETHERSCAN_API_KEY from environment - // and pass the actual value directly to the verification command - const mainnetApiKey = process.env.MAINNET_ETHERSCAN_API_KEY - if (mainnetApiKey) { - forgeArgs.push('-e', mainnetApiKey) - } else { - console.warn( - 'MAINNET_ETHERSCAN_API_KEY not found in environment, verification may fail' - ) - } } // Execute the verification command @@ -374,7 +361,7 @@ export class LifiContracts { network: string, privateKey: Secret, environment?: string - ): Promise { + ): Promise { const env = environment || 'production' // Read network configuration from networks.json @@ -472,9 +459,9 @@ export class LifiContracts { ) } - // Update deployment logs - const loggedContainer = await this.logDeployment( - deploymentContainer, + // Update deployment logs locally + await this.logDeployment( + source, contractName, network, env, @@ -483,9 +470,9 @@ export class LifiContracts { deploySalt ) - // Attempt contract verification using the same built container + // Attempt contract verification using the deployment container const finalContainer = await this.attemptVerification( - loggedContainer, + deploymentContainer, source, contractName, contractAddress, @@ -495,13 +482,33 @@ export class LifiContracts { env ) - return finalContainer + // Create a new directory with the updated deployment files + // We need to read the locally modified files and create a new directory + const deploymentsContainer = dag + .container() + .from('alpine:latest') + .withWorkdir('/deployments') + + // Read all files from the local deployments directory and add them to the container + const deploymentFiles = await fs.readdir('./deployments') + let containerWithFiles = deploymentsContainer + + for (const file of deploymentFiles) { + if (file.endsWith('.json')) { + const content = await fs.readFile(`./deployments/${file}`, 'utf-8') + containerWithFiles = containerWithFiles.withNewFile( + `/deployments/${file}`, + content + ) + } + } + + return containerWithFiles.directory('/deployments') } /** - * Update deployment logs to mark contract as verified using TypeScript + * Update deployment logs to mark contract as verified locally * - * @param container - Container to execute the update in * @param contractName - Name of the verified contract * @param network - Target network name * @param environment - Deployment environment @@ -509,44 +516,29 @@ export class LifiContracts { */ @func() async updateVerificationLogs( - container: Container, contractName: string, network: string, environment: string, contractAddress: string - ): Promise { + ): Promise { const fileSuffix = environment === 'production' ? '' : '.staging' - const deploymentFile = `deployments/${network}${fileSuffix}.json` - const logFile = 'deployments/_deployments_log_file.json' + const deploymentFile = `./deployments/${network}${fileSuffix}.json` + const logFile = './deployments/_deployments_log_file.json' // Read current deployment files - const readDeploymentFile = container.withExec([ - 'sh', - '-c', - ` - if [ -f "/workspace/${deploymentFile}" ]; then - cat "/workspace/${deploymentFile}" - else - echo '{}' - fi - `, - ]) - - const currentDeploymentsRaw = await readDeploymentFile.stdout() - - const readLogFile = container.withExec([ - 'sh', - '-c', - ` - if [ -f "/workspace/${logFile}" ]; then - cat "/workspace/${logFile}" - else - echo '{}' - fi - `, - ]) + let currentDeploymentsRaw = '{}' + try { + currentDeploymentsRaw = await fs.readFile(deploymentFile, 'utf-8') + } catch (e) { + // File doesn't exist, use empty object + } - const currentLogsRaw = await readLogFile.stdout() + let currentLogsRaw = '{}' + try { + currentLogsRaw = await fs.readFile(logFile, 'utf-8') + } catch (e) { + // File doesn't exist, use empty object + } // Parse and update deployment data using TypeScript let currentDeployments: any = {} @@ -593,72 +585,50 @@ export class LifiContracts { const updatedDeployments = JSON.stringify(currentDeployments, null, 2) const updatedLogs = JSON.stringify(currentLogs, null, 2) - const writeDeploymentFile = container.withNewFile( - `/workspace/${deploymentFile}`, - updatedDeployments - ) + await fs.writeFile(deploymentFile, updatedDeployments) + await fs.writeFile(logFile, updatedLogs) - const writeLogFile = writeDeploymentFile.withNewFile( - `/workspace/${logFile}`, - updatedLogs + console.log( + `Contract verification completed successfully for ${contractName} at ${contractAddress}` ) - - // Log success message - return writeLogFile.withExec([ - 'sh', - '-c', - ` - echo "Contract verification completed successfully for ${contractName} at ${contractAddress}" - echo "Updated network deployment file: ${deploymentFile}" - echo "Updated master log file: ${logFile}" - `, - ]) + console.log(`Updated network deployment file: ${deploymentFile}`) + console.log(`Updated master log file: ${logFile}`) } /** * Log deployment details to deployment files */ private async logDeployment( - container: Container, + source: Directory, contractName: string, network: string, environment: string, contractAddress: string, constructorArgs: string, deploySalt: string - ): Promise { + ): Promise { const fileSuffix = environment === 'production' ? '' : '.staging' - const deploymentFile = `deployments/${network}${fileSuffix}.json` - const logFile = 'deployments/_deployments_log_file.json' - - // Read current deployment files or create empty ones - const readDeploymentFile = container.withExec([ - 'sh', - '-c', - ` - if [ -f "/workspace/${deploymentFile}" ]; then - cat "/workspace/${deploymentFile}" - else - echo '{}' - fi - `, - ]) - - const currentDeploymentsRaw = await readDeploymentFile.stdout() + const deploymentFileName = `${network}${fileSuffix}.json` + const logFileName = '_deployments_log_file.json' - const readLogFile = container.withExec([ - 'sh', - '-c', - ` - if [ -f "/workspace/${logFile}" ]; then - cat "/workspace/${logFile}" - else - echo '{}' - fi - `, - ]) + // Read current deployment files from source directory or create empty ones + let currentDeploymentsRaw = '{}' + try { + const deploymentFile = source + .directory('deployments') + .file(deploymentFileName) + currentDeploymentsRaw = await deploymentFile.contents() + } catch (e) { + // File doesn't exist, use empty object + } - const currentLogsRaw = await readLogFile.stdout() + let currentLogsRaw = '{}' + try { + const logFile = source.directory('deployments').file(logFileName) + currentLogsRaw = await logFile.contents() + } catch (e) { + // File doesn't exist, use empty object + } // Parse and update deployment data using TypeScript const timestamp = new Date() @@ -723,21 +693,24 @@ export class LifiContracts { VERIFIED: 'false', }) - // Write updated files + // Write updated files locally using fs const updatedDeployments = JSON.stringify(currentDeployments, null, 2) const updatedLogs = JSON.stringify(currentLogs, null, 2) - const writeDeploymentFile = container.withNewFile( - `/workspace/${deploymentFile}`, + // Ensure deployments directory exists + await fs.mkdir('./deployments', { recursive: true }) + + await fs.writeFile( + `./deployments/${deploymentFileName}`, updatedDeployments ) + await fs.writeFile(`./deployments/${logFileName}`, updatedLogs) - const writeLogFile = writeDeploymentFile.withNewFile( - `/workspace/${logFile}`, - updatedLogs + console.log(`Deployment logged for ${contractName} at ${contractAddress}`) + console.log( + `Updated network deployment file: ./deployments/${deploymentFileName}` ) - - return writeLogFile + console.log(`Updated master log file: ./deployments/${logFileName}`) } /** @@ -782,16 +755,15 @@ export class LifiContracts { `, ]) - // Update deployment logs using TypeScript method - const verifiedContainer = await this.updateVerificationLogs( - logContainer, + // Update deployment logs locally + await this.updateVerificationLogs( contractName, network, environment, contractAddress ) - return verifiedContainer + return logContainer } catch (error) { // If verification fails, continue with unverified deployment console.warn(`Contract verification failed: ${error}`) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 197a7ea0f..8d2f8712a 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -13115,6 +13115,14 @@ "CONSTRUCTOR_ARGS": "0x00000000000000000000000011f11121df7256c40339393b0fb045321022ce44", "SALT": "", "VERIFIED": "true" + }, + { + "ADDRESS": "0xC95C61fC82CCEA4981703D3B2AA3178B7409ce89", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-07-07 15:26:43", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62", + "SALT": "0x880ebbe99fa29aec1e7a04f07871b23a6d976e93ee931b06bc04e14b5b6ba489", + "VERIFIED": "true" } ] }, @@ -35219,4 +35227,4 @@ } } } -} +} \ No newline at end of file diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index 978a15908..7db4d2219 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -15,7 +15,13 @@ "NXTPFacet": "0x238502aDc8ca550723CBE78543c8B757599A21cC", "StargateFacet": "0x7D1940fDfF0B37c137B105ce7967B3B86DB42648", "PeripheryRegistryFacet": "0x69cb467EfD8044ac9eDB88F363309ab1cbFA0A15", - "ERC20Proxy": "0x5741A7FfE7c39Ca175546a54985fA79211290b51", + "ERC20Proxy": { + "address": "0xC95C61fC82CCEA4981703D3B2AA3178B7409ce89", + "constructorArgs": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62", + "deploySalt": "0x880ebbe99fa29aec1e7a04f07871b23a6d976e93ee931b06bc04e14b5b6ba489", + "timestamp": "2025-07-07 15:26:43", + "verified": true + }, "Executor": "0x2dfaDAB8266483beD9Fd9A292Ce56596a2D1378D", "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "FeeCollector": "0xB0210dE78E28e2633Ca200609D9f528c13c26cD9", From 327d20cfd06337e1e4bde564345bd85bd5d42584 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Mon, 7 Jul 2025 18:32:45 +0300 Subject: [PATCH 11/37] fix --- .dagger/src/index.ts | 5 +- deployments/_deployments_log_file.json | 222 ++++++++++++++++++++++++- deployments/arbitrum.json | 8 +- 3 files changed, 218 insertions(+), 17 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 7049bd5e5..dff11c3b7 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -459,6 +459,9 @@ export class LifiContracts { ) } + // Get salt for logging from environment variable (different from deployment salt) + const logSalt = process.env.SALT || '' + // Update deployment logs locally await this.logDeployment( source, @@ -467,7 +470,7 @@ export class LifiContracts { env, contractAddress, constructorArgs, - deploySalt + logSalt ) // Attempt contract verification using the deployment container diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 8d2f8712a..32a0cbc21 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -4760,6 +4760,18 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xc7576334Fb5184B398fCC5ce0f5d237d9bb8BB22", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-03 14:54:59", + "CONSTRUCTOR_ARGS": "0x", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "linea": { @@ -13115,14 +13127,6 @@ "CONSTRUCTOR_ARGS": "0x00000000000000000000000011f11121df7256c40339393b0fb045321022ce44", "SALT": "", "VERIFIED": "true" - }, - { - "ADDRESS": "0xC95C61fC82CCEA4981703D3B2AA3178B7409ce89", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-07-07 15:26:43", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62", - "SALT": "0x880ebbe99fa29aec1e7a04f07871b23a6d976e93ee931b06bc04e14b5b6ba489", - "VERIFIED": "true" } ] }, @@ -28158,6 +28162,16 @@ "SALT": "", "VERIFIED": "true" } + ], + "1.0.2": [ + { + "ADDRESS": "0xa12D48dBf4248d702D5b41e271742914D7d8A321", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-26 09:08:01", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000000000000000000000000000000000000000000000", + "SALT": "20250624", + "VERIFIED": "true" + } ] } }, @@ -29395,6 +29409,28 @@ "VERIFIED": "false" } ] + }, + "staging": { + "1.10.0": [ + { + "ADDRESS": "0xDD6c0EDb3F9EB93B8D5b02c0DbAEEcf9d74c7C76", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-13 10:23:16", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "true" + } + ], + "1.11.0": [ + { + "ADDRESS": "0xc65f68DF59474e44026e7Cf4d189384B42122AEA", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-18 10:08:38", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "kaia": { @@ -29540,6 +29576,16 @@ "SALT": "", "VERIFIED": "true" } + ], + "1.11.0": [ + { + "ADDRESS": "0xa396390CE55bA9bA3A706c4924aef9679D36fbd9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-24 17:05:55", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "20250624", + "VERIFIED": true + } ] } }, @@ -29663,6 +29709,28 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.10.0": [ + { + "ADDRESS": "0x7c2d5b41b3ade72CCCF0f936377931108F6b71A9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-05-07 10:23:47", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "true" + } + ], + "1.11.0": [ + { + "ADDRESS": "0x8Bf50e238E8cca234073C84C926894A6f300d597", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-18 10:05:03", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "blast": { @@ -29839,6 +29907,28 @@ "VERIFIED": "true" } ] + }, + "staging": { + "1.10.0": [ + { + "ADDRESS": "0x7c2d5b41b3ade72CCCF0f936377931108F6b71A9", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-10 11:03:40", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "true" + } + ], + "1.11.0": [ + { + "ADDRESS": "0x163A7d7D057De1B5e6c61c1101B669C2e72fc314", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-18 10:09:38", + "CONSTRUCTOR_ARGS": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000156cebba59deb2cb23742f70dcb0a11cc775591f0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000d38743b48d26743c0ec6898d699394fbc94657ee", + "SALT": "", + "VERIFIED": "true" + } + ] } }, "metis": { @@ -34661,6 +34751,48 @@ } ] } + }, + "celo": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0x66dAAa672cdB57F1F00CeD0408D6B7ca8ddb3399", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-26 17:36:53", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000007f4babd2c7d35221e72ab67ea72cba99573a0089000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "metis": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0x44a1f8744c85665553eFb8C24fd4428B1D3D8e1B", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-30 00:00:00", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c56043daac3a26ad451abc7610f04f53cc4412e5000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "cronos": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0x49cf6ff64B9B617Bee2B4Ce12eb0248D1656a46f", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-30 17:14:22", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000c56043daac3a26ad451abc7610f04f53cc4412e5000000000000000000000000f70da97812cb96acdf810712aa562db8dfa3dbef", + "SALT": "", + "VERIFIED": "true" + } + ] + } } }, "GetGasFacet": { @@ -35169,6 +35301,34 @@ } ] } + }, + "abstract": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0x8AA1575399be4E9a49cAF5ecFB4878A2982A5cFD", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-07-04 08:08:19", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000005501e28f588b3a246a590a391a7ae7b2ba065dde", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "zksync": { + "production": { + "1.0.0": [ + { + "ADDRESS": "0x167e8431f2C65220FC0De0FA752A5661A1281102", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-07-04 08:10:20", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000a0246202ac2537c9978be8940d51dc70b3d38426", + "SALT": "", + "VERIFIED": "true" + } + ] + } } }, "LidoWrapper": { @@ -35226,5 +35386,49 @@ ] } } + }, + "PioneerFacet": { + "arbitrum": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x371E61d9DC497C506837DFA47B8dccEF1da30459", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-26 15:57:47", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000e92ed34fd131d5480f56ccb5f73dc799dba8f459", + "SALT": "", + "VERIFIED": false + } + ] + } + }, + "soneium": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0xa5A269F733E960D3F1E4509726438d3263BDb20d", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-04 14:04:00", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000e92ed34fd131d5480f56ccb5f73dc799dba8f459", + "SALT": "", + "VERIFIED": "true" + } + ] + } + }, + "optimism": { + "staging": { + "1.0.0": [ + { + "ADDRESS": "0x371E61d9DC497C506837DFA47B8dccEF1da30459", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-06-26 16:07:41", + "CONSTRUCTOR_ARGS": "0x000000000000000000000000e92ed34fd131d5480f56ccb5f73dc799dba8f459", + "SALT": "", + "VERIFIED": true + } + ] + } + } } -} \ No newline at end of file +} diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index 7db4d2219..978a15908 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -15,13 +15,7 @@ "NXTPFacet": "0x238502aDc8ca550723CBE78543c8B757599A21cC", "StargateFacet": "0x7D1940fDfF0B37c137B105ce7967B3B86DB42648", "PeripheryRegistryFacet": "0x69cb467EfD8044ac9eDB88F363309ab1cbFA0A15", - "ERC20Proxy": { - "address": "0xC95C61fC82CCEA4981703D3B2AA3178B7409ce89", - "constructorArgs": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62", - "deploySalt": "0x880ebbe99fa29aec1e7a04f07871b23a6d976e93ee931b06bc04e14b5b6ba489", - "timestamp": "2025-07-07 15:26:43", - "verified": true - }, + "ERC20Proxy": "0x5741A7FfE7c39Ca175546a54985fA79211290b51", "Executor": "0x2dfaDAB8266483beD9Fd9A292Ce56596a2D1378D", "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "FeeCollector": "0xB0210dE78E28e2633Ca200609D9f528c13c26cD9", From 99135fe40a0a82b1760923e8abc11fb2cdb8a87a Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 09:45:43 +0300 Subject: [PATCH 12/37] update --- .dagger/src/index.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index dff11c3b7..3faf6e20c 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -654,13 +654,8 @@ export class LifiContracts { } // Update deployment data (network-specific file) - currentDeployments[contractName] = { - address: contractAddress, - constructorArgs: constructorArgs, - deploySalt: deploySalt, - timestamp: timestamp, - verified: false, - } + // For network files, just store the address as a string (matching existing format) + currentDeployments[contractName] = contractAddress // Update master log with nested structure: contractName -> network -> environment -> version -> array if (!currentLogs[contractName]) { From b443915b558cc8d9eb94141047107323162f13b7 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 10:39:09 +0300 Subject: [PATCH 13/37] update --- .dagger/src/index.ts | 96 ++++++++++++++++++++------ deployments/_deployments_log_file.json | 10 ++- deployments/arbitrum.json | 2 +- 3 files changed, 83 insertions(+), 25 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 3faf6e20c..07e08376c 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -376,7 +376,7 @@ export class LifiContracts { const networkConfig = networks[network] as NetworkConfig // Build the project first with network-specific versions - const builtContainer = this.buildProject( + let builtContainer = this.buildProject( source, networkConfig.deployedWithSolcVersion, networkConfig.deployedWithEvmVersion @@ -384,7 +384,7 @@ export class LifiContracts { const scriptPath = `script/deploy/facets/Deploy${contractName}.s.sol` - // Generate deployment salt + // Generate deployment salt exactly like the bash script let deploySalt: string // Read the bytecode from compiled artifacts const artifactPath = `/workspace/out/${contractName}.sol/${contractName}.json` @@ -392,21 +392,30 @@ export class LifiContracts { const artifactContent = await artifactContainer.stdout() const artifact = JSON.parse(artifactContent) - const bytecode = artifact.bytecode?.object || artifact.bytecode + let bytecode = artifact.bytecode?.object || artifact.bytecode if (!bytecode) { throw new Error(`No bytecode found for contract ${contractName}`) } - // Generate SHA256 hash of the bytecode - const hashContainer = builtContainer.withExec([ - 'sh', - '-c', - `echo -n "${bytecode}" | sha256sum | cut -d' ' -f1`, - ]) + // Get SALT from environment variable (can be empty) + const salt = process.env.SALT || '' + + // Validate SALT format if provided (must have even number of digits) + if (salt && salt.length % 2 !== 0) { + throw new Error( + 'SALT environment variable has odd number of digits (must be even digits)' + ) + } + + // Create salt input by concatenating bytecode and SALT (same as bash: SALT_INPUT="$BYTECODE""$SALT") + const saltInput = bytecode + salt + + // Generate DEPLOYSALT using cast keccak (same as bash: DEPLOYSALT=$(cast keccak "$SALT_INPUT")) + builtContainer = builtContainer.withExec(['cast', 'keccak', saltInput]) - const hash = await hashContainer.stdout() - deploySalt = `0x${hash.trim()}` + const keccakResult = await builtContainer.stdout() + deploySalt = keccakResult.trim() // Execute deployment const deploymentContainer = this.deployContractInternal( @@ -433,34 +442,75 @@ export class LifiContracts { // Extract deployment results from the output const deploymentOutput = await deploymentContainer.stdout() - // Parse the JSON output to extract contract address and constructor args + // Parse the deployment output using the same logic as deploySingleContract.sh let contractAddress = '' let constructorArgs = '0x' - try { - const result = JSON.parse(deploymentOutput) - if (result.returns && result.returns.deployed) { - contractAddress = result.returns.deployed.value + // Extract the JSON blob that starts with {"logs": + const jsonMatch = deploymentOutput.match(/\{"logs":.*/) + let cleanData = '' + + if (jsonMatch) { + cleanData = jsonMatch[0] + + try { + const result = JSON.parse(cleanData) + + // Try extracting from `.returns.deployed.value` (primary method) + if ( + result.returns && + result.returns.deployed && + result.returns.deployed.value + ) { + contractAddress = result.returns.deployed.value + } + + // Extract constructor args from `.returns.constructorArgs.value` + if ( + result.returns && + result.returns.constructorArgs && + result.returns.constructorArgs.value + ) { + constructorArgs = result.returns.constructorArgs.value + } + } catch (e) { + // JSON parsing failed, continue to fallback methods } - if (result.returns && result.returns.constructorArgs) { - constructorArgs = result.returns.constructorArgs.value + } + + // Fallback: try to extract from Etherscan "contract_address" + if (!contractAddress) { + const etherscanMatch = deploymentOutput.match( + /"contract_address"\s*:\s*"(0x[a-fA-F0-9]{40})"/ + ) + if (etherscanMatch) { + contractAddress = etherscanMatch[1] } - } catch (e) { - // If parsing fails, try to extract address from logs + } + + // Last resort: use first 0x-prefixed address in output + if (!contractAddress) { const addressMatch = deploymentOutput.match(/0x[a-fA-F0-9]{40}/) if (addressMatch) { contractAddress = addressMatch[0] } } + // Validate the format of the extracted address + if (!contractAddress || !/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) { + throw new Error( + 'Failed to extract valid contract address from deployment output' + ) + } + if (!contractAddress) { throw new Error( 'Failed to extract contract address from deployment output' ) } - // Get salt for logging from environment variable (different from deployment salt) - const logSalt = process.env.SALT || '' + // Use original SALT for logging (can be empty string) + const logSalt = salt // Update deployment logs locally await this.logDeployment( @@ -474,7 +524,7 @@ export class LifiContracts { ) // Attempt contract verification using the deployment container - const finalContainer = await this.attemptVerification( + await this.attemptVerification( deploymentContainer, source, contractName, diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 32a0cbc21..f1a6623da 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -13127,6 +13127,14 @@ "CONSTRUCTOR_ARGS": "0x00000000000000000000000011f11121df7256c40339393b0fb045321022ce44", "SALT": "", "VERIFIED": "true" + }, + { + "ADDRESS": "0xa22eB01f143edd5d35C9463bd438FC4E3A1AA013", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-07-08 07:37:15", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62", + "SALT": "", + "VERIFIED": "false" } ] }, @@ -35431,4 +35439,4 @@ } } } -} +} \ No newline at end of file diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index 978a15908..20300e6b6 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -15,7 +15,7 @@ "NXTPFacet": "0x238502aDc8ca550723CBE78543c8B757599A21cC", "StargateFacet": "0x7D1940fDfF0B37c137B105ce7967B3B86DB42648", "PeripheryRegistryFacet": "0x69cb467EfD8044ac9eDB88F363309ab1cbFA0A15", - "ERC20Proxy": "0x5741A7FfE7c39Ca175546a54985fA79211290b51", + "ERC20Proxy": "0xa22eB01f143edd5d35C9463bd438FC4E3A1AA013", "Executor": "0x2dfaDAB8266483beD9Fd9A292Ce56596a2D1378D", "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "FeeCollector": "0xB0210dE78E28e2633Ca200609D9f528c13c26cD9", From 28f399103184545e23beb184d1ea2933780f183b Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 11:10:32 +0300 Subject: [PATCH 14/37] deploy-to-all-networks --- .dagger/src/index.ts | 121 +++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 44 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 07e08376c..1e66d08d1 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -12,7 +12,6 @@ import { object, func, } from '@dagger.io/dagger' -import * as fs from 'fs/promises' interface NetworkConfig { chainId: number @@ -512,8 +511,8 @@ export class LifiContracts { // Use original SALT for logging (can be empty string) const logSalt = salt - // Update deployment logs locally - await this.logDeployment( + // Update deployment logs and get updated source + source = await this.logDeployment( source, contractName, network, @@ -535,33 +534,58 @@ export class LifiContracts { env ) - // Create a new directory with the updated deployment files - // We need to read the locally modified files and create a new directory - const deploymentsContainer = dag - .container() - .from('alpine:latest') - .withWorkdir('/deployments') - - // Read all files from the local deployments directory and add them to the container - const deploymentFiles = await fs.readdir('./deployments') - let containerWithFiles = deploymentsContainer - - for (const file of deploymentFiles) { - if (file.endsWith('.json')) { - const content = await fs.readFile(`./deployments/${file}`, 'utf-8') - containerWithFiles = containerWithFiles.withNewFile( - `/deployments/${file}`, - content + // Return the updated deployments directory from source + return source.directory('deployments') + } + + /** + * Deploy a smart contract to multiple networks + * + * @param source - Source directory containing the project root + * @param contractName - Name of the contract to deploy (e.g., "AcrossFacet") + * @param privateKey - Private key secret for deployment + * @param environment - Deployment environment ("staging" or "production", defaults to "production") + */ + @func() + async deployToAllNetworks( + source: Directory, + contractName: string, + privateKey: Secret, + environment?: string + ): Promise { + const networks = ['optimism', 'arbitrum', 'base', 'boba', 'rootstock'] + let updatedSource = source + + for (const network of networks) { + try { + const deploymentResult = await this.deployContract( + updatedSource, + contractName, + network, + privateKey, + environment + ) + // Update source with the changes from this deployment + updatedSource = updatedSource.withDirectory( + 'deployments', + deploymentResult + ) + console.log(`✅ Successfully deployed ${contractName} to ${network}`) + } catch (error) { + console.error( + `❌ Failed to deploy ${contractName} to ${network}: ${error}` ) + // Continue to next network } } - return containerWithFiles.directory('/deployments') + return updatedSource.directory('deployments') } /** * Update deployment logs to mark contract as verified locally * + * @param source - Source directory containing the project root * @param contractName - Name of the verified contract * @param network - Target network name * @param environment - Deployment environment @@ -569,26 +593,31 @@ export class LifiContracts { */ @func() async updateVerificationLogs( + source: Directory, contractName: string, network: string, environment: string, contractAddress: string - ): Promise { + ): Promise { const fileSuffix = environment === 'production' ? '' : '.staging' - const deploymentFile = `./deployments/${network}${fileSuffix}.json` - const logFile = './deployments/_deployments_log_file.json' + const deploymentFileName = `${network}${fileSuffix}.json` + const logFileName = '_deployments_log_file.json' - // Read current deployment files + // Read current deployment files from source directory let currentDeploymentsRaw = '{}' try { - currentDeploymentsRaw = await fs.readFile(deploymentFile, 'utf-8') + const deploymentFile = source + .directory('deployments') + .file(deploymentFileName) + currentDeploymentsRaw = await deploymentFile.contents() } catch (e) { // File doesn't exist, use empty object } let currentLogsRaw = '{}' try { - currentLogsRaw = await fs.readFile(logFile, 'utf-8') + const logFile = source.directory('deployments').file(logFileName) + currentLogsRaw = await logFile.contents() } catch (e) { // File doesn't exist, use empty object } @@ -634,18 +663,23 @@ export class LifiContracts { ) } - // Write updated files + // Write updated files to source directory const updatedDeployments = JSON.stringify(currentDeployments, null, 2) const updatedLogs = JSON.stringify(currentLogs, null, 2) - await fs.writeFile(deploymentFile, updatedDeployments) - await fs.writeFile(logFile, updatedLogs) + const updatedSource = source + .withNewFile(`deployments/${deploymentFileName}`, updatedDeployments) + .withNewFile(`deployments/${logFileName}`, updatedLogs) console.log( `Contract verification completed successfully for ${contractName} at ${contractAddress}` ) - console.log(`Updated network deployment file: ${deploymentFile}`) - console.log(`Updated master log file: ${logFile}`) + console.log( + `Updated network deployment file: deployments/${deploymentFileName}` + ) + console.log(`Updated master log file: deployments/${logFileName}`) + + return updatedSource } /** @@ -659,7 +693,7 @@ export class LifiContracts { contractAddress: string, constructorArgs: string, deploySalt: string - ): Promise { + ): Promise { const fileSuffix = environment === 'production' ? '' : '.staging' const deploymentFileName = `${network}${fileSuffix}.json` const logFileName = '_deployments_log_file.json' @@ -741,24 +775,22 @@ export class LifiContracts { VERIFIED: 'false', }) - // Write updated files locally using fs + // Write updated files to source directory const updatedDeployments = JSON.stringify(currentDeployments, null, 2) const updatedLogs = JSON.stringify(currentLogs, null, 2) - // Ensure deployments directory exists - await fs.mkdir('./deployments', { recursive: true }) - - await fs.writeFile( - `./deployments/${deploymentFileName}`, - updatedDeployments - ) - await fs.writeFile(`./deployments/${logFileName}`, updatedLogs) + // Update the source directory with new deployment files + let updatedSource = source + .withNewFile(`deployments/${deploymentFileName}`, updatedDeployments) + .withNewFile(`deployments/${logFileName}`, updatedLogs) console.log(`Deployment logged for ${contractName} at ${contractAddress}`) console.log( - `Updated network deployment file: ./deployments/${deploymentFileName}` + `Updated network deployment file: deployments/${deploymentFileName}` ) - console.log(`Updated master log file: ./deployments/${logFileName}`) + console.log(`Updated master log file: deployments/${logFileName}`) + + return updatedSource } /** @@ -805,6 +837,7 @@ export class LifiContracts { // Update deployment logs locally await this.updateVerificationLogs( + source, contractName, network, environment, From fb5b50a349d534effdcbbc4251bd4720e8aa4df7 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 11:20:11 +0300 Subject: [PATCH 15/37] log faile networks --- .dagger/src/index.ts | 94 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 1e66d08d1..c721ab5d0 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -572,9 +572,30 @@ export class LifiContracts { ) console.log(`✅ Successfully deployed ${contractName} to ${network}`) } catch (error) { + // Capture full error details for logging + let errorMessage = 'Unknown error' + if (error instanceof Error) { + errorMessage = `${error.name}: ${error.message}` + if (error.stack) { + errorMessage += `\nStack: ${error.stack}` + } + } else { + errorMessage = String(error) + } + console.error( - `❌ Failed to deploy ${contractName} to ${network}: ${error}` + `❌ Failed to deploy ${contractName} to ${network}: ${errorMessage}` ) + + // Log the failure to failed_deployments_log.json + updatedSource = await this.logFailedDeployment( + updatedSource, + contractName, + network, + environment || 'production', + errorMessage + ) + // Continue to next network } } @@ -682,6 +703,77 @@ export class LifiContracts { return updatedSource } + /** + * Log failed deployment details to failed_deployments_log.json + */ + private async logFailedDeployment( + source: Directory, + contractName: string, + network: string, + environment: string, + errorMessage: string + ): Promise { + const failedLogFileName = 'failed_deployments_log.json' + + // Read current failed deployments log + let currentFailedLogsRaw = '{}' + try { + const failedLogFile = source + .directory('deployments') + .file(failedLogFileName) + currentFailedLogsRaw = await failedLogFile.contents() + } catch (e) { + // File doesn't exist, use empty object + } + + // Parse and update failed deployment data + const timestamp = new Date() + .toISOString() + .replace('T', ' ') + .replace(/\.\d{3}Z$/, '') + + let currentFailedLogs: any = {} + try { + currentFailedLogs = JSON.parse(currentFailedLogsRaw.trim() || '{}') + } catch (e) { + currentFailedLogs = {} + } + + // Create nested structure: contractName -> network -> environment -> array + if (!currentFailedLogs[contractName]) { + currentFailedLogs[contractName] = {} + } + if (!currentFailedLogs[contractName][network]) { + currentFailedLogs[contractName][network] = {} + } + if (!currentFailedLogs[contractName][network][environment]) { + currentFailedLogs[contractName][network][environment] = [] + } + + // Add new failed deployment entry + currentFailedLogs[contractName][network][environment].push({ + TIMESTAMP: timestamp, + ERROR_MESSAGE: errorMessage, + ENVIRONMENT: environment, + }) + + // Write updated failed deployments log to source directory + const updatedFailedLogs = JSON.stringify(currentFailedLogs, null, 2) + const updatedSource = source.withNewFile( + `deployments/${failedLogFileName}`, + updatedFailedLogs + ) + + console.log( + `Failed deployment logged for ${contractName} on ${network}: ${errorMessage}` + ) + console.log( + `Updated failed deployments log: deployments/${failedLogFileName}` + ) + + return updatedSource + } + /** * Log deployment details to deployment files */ From c631a4ffbf05aab6c15b547183abef2fa7b0f05d Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 11:31:20 +0300 Subject: [PATCH 16/37] revert logs --- deployments/_deployments_log_file.json | 10 +--------- deployments/arbitrum.json | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index f1a6623da..32a0cbc21 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -13127,14 +13127,6 @@ "CONSTRUCTOR_ARGS": "0x00000000000000000000000011f11121df7256c40339393b0fb045321022ce44", "SALT": "", "VERIFIED": "true" - }, - { - "ADDRESS": "0xa22eB01f143edd5d35C9463bd438FC4E3A1AA013", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-07-08 07:37:15", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000002b2c52b1b63c4bfc7f1a310a1734641d8e34de62", - "SALT": "", - "VERIFIED": "false" } ] }, @@ -35439,4 +35431,4 @@ } } } -} \ No newline at end of file +} diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index 20300e6b6..978a15908 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -15,7 +15,7 @@ "NXTPFacet": "0x238502aDc8ca550723CBE78543c8B757599A21cC", "StargateFacet": "0x7D1940fDfF0B37c137B105ce7967B3B86DB42648", "PeripheryRegistryFacet": "0x69cb467EfD8044ac9eDB88F363309ab1cbFA0A15", - "ERC20Proxy": "0xa22eB01f143edd5d35C9463bd438FC4E3A1AA013", + "ERC20Proxy": "0x5741A7FfE7c39Ca175546a54985fA79211290b51", "Executor": "0x2dfaDAB8266483beD9Fd9A292Ce56596a2D1378D", "Receiver": "0x050e198E36A73a1e32F15C3afC58C4506d82f657", "FeeCollector": "0xB0210dE78E28e2633Ca200609D9f528c13c26cD9", From 5d3553756d5f656f48f108f89154055b6542c0dc Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 11:31:54 +0300 Subject: [PATCH 17/37] remove fallbacks --- .dagger/src/index.ts | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index c721ab5d0..109928862 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -352,6 +352,7 @@ export class LifiContracts { * @param network - Target network name (e.g., "arbitrum", "mainnet") * @param privateKey - Private key secret for deployment * @param environment - Deployment environment ("staging" or "production", defaults to "production") + * @returns Updated source directory with deployment logs */ @func() async deployContract( @@ -477,31 +478,6 @@ export class LifiContracts { } } - // Fallback: try to extract from Etherscan "contract_address" - if (!contractAddress) { - const etherscanMatch = deploymentOutput.match( - /"contract_address"\s*:\s*"(0x[a-fA-F0-9]{40})"/ - ) - if (etherscanMatch) { - contractAddress = etherscanMatch[1] - } - } - - // Last resort: use first 0x-prefixed address in output - if (!contractAddress) { - const addressMatch = deploymentOutput.match(/0x[a-fA-F0-9]{40}/) - if (addressMatch) { - contractAddress = addressMatch[0] - } - } - - // Validate the format of the extracted address - if (!contractAddress || !/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) { - throw new Error( - 'Failed to extract valid contract address from deployment output' - ) - } - if (!contractAddress) { throw new Error( 'Failed to extract contract address from deployment output' @@ -534,8 +510,8 @@ export class LifiContracts { env ) - // Return the updated deployments directory from source - return source.directory('deployments') + // Return the full updated source directory + return source } /** @@ -558,18 +534,14 @@ export class LifiContracts { for (const network of networks) { try { - const deploymentResult = await this.deployContract( + // deployContract returns an updated source directory with the new deployment logs + updatedSource = await this.deployContract( updatedSource, contractName, network, privateKey, environment ) - // Update source with the changes from this deployment - updatedSource = updatedSource.withDirectory( - 'deployments', - deploymentResult - ) console.log(`✅ Successfully deployed ${contractName} to ${network}`) } catch (error) { // Capture full error details for logging From df6d3909162e25f05ef42fb9c3fbbb9077968849 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 11:51:41 +0300 Subject: [PATCH 18/37] tweaks --- .dagger/bun.lockb | Bin 0 -> 1663 bytes .dagger/src/index.ts | 59 ++++++++++++++++++++++++++---------------- .dagger/tsconfig.json | 10 ++++--- dagger.json | 2 +- 4 files changed, 45 insertions(+), 26 deletions(-) create mode 100755 .dagger/bun.lockb diff --git a/.dagger/bun.lockb b/.dagger/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..fbc82a6293d6f8b2fd5d415f0e357785fd4007ec GIT binary patch literal 1663 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_lP!Nybb5 zcAj1L=TpXai=}-BRwvy0ys%&AGT*M8#%1-FnBTJk6#)S=gks=8qZ^?7a+m@rA3||4 zFf^p(mjK;?5T38vprhTInpC|pW>S6i)pv{tDY94~Ux0uZ5ZeIl134JvN~{h7ilYOV z{ve=)DNw&4kS5hlATwa*z-W*?*v0?x|0C4`kYV@$&^$&4h`Go{vn&r!ieB09B#Lad1Zf1?exP198xI_+i$D-_=Y$0c}2aFzxCwY9t~U8zMF3PZ5i>q zH~!$SL^2oIUo2blLj#S1cUkX}Jo+H_Hs4N(ye0lJ`JEFR`#g087Ct!bwlK>4AN#SO z%RPsZUoSW}_u{r~GZgdpKV7}FDfHC}&RAqeBKeC2%7)QMQWP<9SVxhWXqpc|)7up& z&1F+ul&qJTS6q^qlcNXAeR?59si}4fMg|JSnN_LzX*vof3W+(H>3R8Sz`Ozn|NcV& zNB|t}&;a3p(p)yB#zuBPy*Nw(`PTxf)`3VpAbUXi3ZUwj!1YBNnZ=e`09|bcx6csa zYFPe;}FYdy8$}$rkP^DK1Ve z0-6_lS)nl~&HD!^oOyt*YOydq!&t%hF9GU?=Ww@x{A!5Jt6+OdDhpDJlZ!G7N+32t zOwTAODJZtm*Dp#<&nzw}s?^IX$jvI&%P&gTM`+O3MdIk1>RIR+>y@Ne;n52TT%bM( SLl+pbhQP1|Y681u5E1~gS|IBH literal 0 HcmV?d00001 diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 109928862..20b90235a 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -33,11 +33,11 @@ export class LifiContracts { * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") */ @func() - buildProject( + async buildProject( source: Directory, solcVersion?: string, evmVersion?: string - ): Container { + ): Promise { let container = dag .container() .from('ghcr.io/foundry-rs/foundry:latest') @@ -66,15 +66,33 @@ export class LifiContracts { .withUser('foundry') .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') + // Read defaults from foundry.toml if versions not provided + let finalSolcVersion = solcVersion + let finalEvmVersion = evmVersion + + if (!solcVersion || !evmVersion) { + const foundryToml = await source.file('foundry.toml').contents() + if (!solcVersion) { + const solcMatch = foundryToml.match( + /solc_version\s*=\s*['"]([^'"]+)['"]/ + ) + finalSolcVersion = solcMatch ? solcMatch[1] : '0.8.29' + } + if (!evmVersion) { + const evmMatch = foundryToml.match(/evm_version\s*=\s*['"]([^'"]+)['"]/) + finalEvmVersion = evmMatch ? evmMatch[1] : 'cancun' + } + } + // Build forge build command with version parameters const buildArgs = ['forge', 'build'] - if (solcVersion) { - buildArgs.push('--use', solcVersion) + if (finalSolcVersion) { + buildArgs.push('--use', finalSolcVersion) } - if (evmVersion) { - buildArgs.push('--evm-version', evmVersion) + if (finalEvmVersion) { + buildArgs.push('--evm-version', finalEvmVersion) } container = container.withExec(buildArgs) @@ -105,7 +123,7 @@ export class LifiContracts { * @param diamondType - Diamond type for CelerIMFacet (optional) */ @func() - deployContractInternal( + async deployContractInternal( source: Directory, scriptPath: string, network: string, @@ -124,18 +142,18 @@ export class LifiContracts { defaultDiamondAddressDeploysalt?: string, deployToDefaultDiamondAddress?: string, diamondType?: string - ): Container { + ): Promise { // Set default values const gasMultiplier = gasEstimateMultiplier || '130' const shouldBroadcast = broadcast !== false const useLegacy = legacy !== false const useSlow = slow !== false const shouldSkipSimulation = skipSimulation === true - const solc = solcVersion || '0.8.29' - const evm = evmVersion || 'cancun' - + // Use provided versions or let buildProject read foundry.toml defaults + const solc = solcVersion || '0.8.29' // fallback only + const evm = evmVersion || 'cancun' // fallback only // Build the project first with the same versions as deployment - const builtContainer = this.buildProject(source, solc, evm) + const builtContainer = await this.buildProject(source, solc, evm) // Mount the deployments directory to the built container const containerWithDeployments = builtContainer.withMountedDirectory( @@ -179,6 +197,7 @@ export class LifiContracts { .withEnvVariable('NETWORK', network) .withEnvVariable('FILE_SUFFIX', fileSuffix) .withSecretVariable('PRIVATE_KEY', privateKey) + .withEnvVariable('SALT', process.env.SALT || '') .withEnvVariable( 'DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS', deployToDefaultDiamondAddress || 'true' @@ -323,7 +342,7 @@ export class LifiContracts { const networkConfig = networks[network] as NetworkConfig // Build the project first to get the same artifacts as deployment - const builtContainer = this.buildProject( + const builtContainer = await this.buildProject( source, networkConfig.deployedWithSolcVersion, networkConfig.deployedWithEvmVersion @@ -375,12 +394,8 @@ export class LifiContracts { const networkConfig = networks[network] as NetworkConfig - // Build the project first with network-specific versions - let builtContainer = this.buildProject( - source, - networkConfig.deployedWithSolcVersion, - networkConfig.deployedWithEvmVersion - ) + // Build the project first with foundry.toml defaults (buildProject will read foundry.toml) + let builtContainer = await this.buildProject(source) const scriptPath = `script/deploy/facets/Deploy${contractName}.s.sol` @@ -418,7 +433,7 @@ export class LifiContracts { deploySalt = keccakResult.trim() // Execute deployment - const deploymentContainer = this.deployContractInternal( + const deploymentContainer = await this.deployContractInternal( source, scriptPath, network, @@ -426,8 +441,8 @@ export class LifiContracts { networkConfig.create3Factory, env === 'production' ? '' : 'staging.', privateKey, // privateKey passed as secret - networkConfig.deployedWithSolcVersion, - networkConfig.deployedWithEvmVersion, + undefined, // solcVersion - use foundry.toml defaults + undefined, // evmVersion - use foundry.toml defaults '130', // gasEstimateMultiplier true, // broadcast true, // legacy diff --git a/.dagger/tsconfig.json b/.dagger/tsconfig.json index 4ec0c2da6..aab5941a2 100644 --- a/.dagger/tsconfig.json +++ b/.dagger/tsconfig.json @@ -6,8 +6,12 @@ "strict": true, "skipLibCheck": true, "paths": { - "@dagger.io/dagger": ["./sdk/index.ts"], - "@dagger.io/dagger/telemetry": ["./sdk/telemetry.ts"] + "@dagger.io/dagger": [ + "./sdk/index.ts" + ], + "@dagger.io/dagger/telemetry": [ + "./sdk/telemetry.ts" + ] } } -} +} \ No newline at end of file diff --git a/dagger.json b/dagger.json index 4ac9def5b..15467eba2 100644 --- a/dagger.json +++ b/dagger.json @@ -1,6 +1,6 @@ { "name": "lifi-contracts", - "engineVersion": "v0.18.10", + "engineVersion": "v0.18.12", "sdk": { "source": "typescript" }, From 9033cfa72d823ca584c8a638209aaa0193dd75c2 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 13:06:29 +0300 Subject: [PATCH 19/37] add script for deploying --- .dagger/src/index.ts | 3 +- script/deploy/deployToAllNetworks.sh | 42 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100755 script/deploy/deployToAllNetworks.sh diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 20b90235a..73666d8fb 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -534,6 +534,7 @@ export class LifiContracts { * * @param source - Source directory containing the project root * @param contractName - Name of the contract to deploy (e.g., "AcrossFacet") + * @param networks - Array of network names to deploy to (e.g., ["arbitrum", "optimism"]) * @param privateKey - Private key secret for deployment * @param environment - Deployment environment ("staging" or "production", defaults to "production") */ @@ -541,10 +542,10 @@ export class LifiContracts { async deployToAllNetworks( source: Directory, contractName: string, + networks: string[], privateKey: Secret, environment?: string ): Promise { - const networks = ['optimism', 'arbitrum', 'base', 'boba', 'rootstock'] let updatedSource = source for (const network of networks) { diff --git a/script/deploy/deployToAllNetworks.sh b/script/deploy/deployToAllNetworks.sh new file mode 100755 index 000000000..02395ea25 --- /dev/null +++ b/script/deploy/deployToAllNetworks.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +source .env +source script/config.sh + +# Extract contract names from script/deploy/facets/ directory +CONTRACTS=$(find script/deploy/facets/ -name "Deploy*.s.sol" -exec basename {} \; | sed 's/^Deploy//; s/\.s\.sol$//' | sort) + +# Use gum to select a single contract +SELECTED_CONTRACT=$(echo "$CONTRACTS" | gum choose \ + --limit=1 \ + --select-if-one \ + --header="Select contract to deploy:") + +# Check if a contract was selected +if [ -z "$SELECTED_CONTRACT" ]; then + echo "No contract selected. Exiting." + exit 1 +fi + +echo "Selected contract: $SELECTED_CONTRACT" + +# Extract network names from config/networks.json, filtering out zkEVM networks +NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) + +# Use gum to select networks with all networks pre-selected +SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ + --no-limit \ + --selected="*" \ + --header="Select networks to deploy to:" \ + --output-delimiter=",") + +# Check if any networks were selected +if [ -z "$SELECTED_NETWORKS" ]; then + echo "No networks selected. Exiting." + exit 1 +fi + +echo "Selected networks: $SELECTED_NETWORKS" + +# Run dagger command with selected contract and networks +dagger -c "deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY | export ./deployments" From f7a622164f18dc21bf874ff20788cd5c7ed45876 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 13:43:40 +0300 Subject: [PATCH 20/37] add evm and solc selection --- .dagger/src/index.ts | 26 +++++++++--- script/deploy/deployToAllNetworks.sh | 62 +++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 73666d8fb..c43b43c86 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -371,6 +371,8 @@ export class LifiContracts { * @param network - Target network name (e.g., "arbitrum", "mainnet") * @param privateKey - Private key secret for deployment * @param environment - Deployment environment ("staging" or "production", defaults to "production") + * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") + * @param solcVersion - Solidity compiler version (e.g., "0.8.29") * @returns Updated source directory with deployment logs */ @func() @@ -379,7 +381,9 @@ export class LifiContracts { contractName: string, network: string, privateKey: Secret, - environment?: string + environment?: string, + evmVersion?: string, + solcVersion?: string ): Promise { const env = environment || 'production' @@ -395,7 +399,11 @@ export class LifiContracts { const networkConfig = networks[network] as NetworkConfig // Build the project first with foundry.toml defaults (buildProject will read foundry.toml) - let builtContainer = await this.buildProject(source) + let builtContainer = await this.buildProject( + source, + solcVersion, + evmVersion + ) const scriptPath = `script/deploy/facets/Deploy${contractName}.s.sol` @@ -441,8 +449,8 @@ export class LifiContracts { networkConfig.create3Factory, env === 'production' ? '' : 'staging.', privateKey, // privateKey passed as secret - undefined, // solcVersion - use foundry.toml defaults - undefined, // evmVersion - use foundry.toml defaults + solcVersion, // solcVersion - use provided version or foundry.toml defaults + evmVersion, // evmVersion - use provided version or foundry.toml defaults '130', // gasEstimateMultiplier true, // broadcast true, // legacy @@ -537,6 +545,8 @@ export class LifiContracts { * @param networks - Array of network names to deploy to (e.g., ["arbitrum", "optimism"]) * @param privateKey - Private key secret for deployment * @param environment - Deployment environment ("staging" or "production", defaults to "production") + * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") + * @param solcVersion - Solidity compiler version (e.g., "0.8.29") */ @func() async deployToAllNetworks( @@ -544,7 +554,9 @@ export class LifiContracts { contractName: string, networks: string[], privateKey: Secret, - environment?: string + environment?: string, + evmVersion?: string, + solcVersion?: string ): Promise { let updatedSource = source @@ -556,7 +568,9 @@ export class LifiContracts { contractName, network, privateKey, - environment + environment, + evmVersion, + solcVersion ) console.log(`✅ Successfully deployed ${contractName} to ${network}`) } catch (error) { diff --git a/script/deploy/deployToAllNetworks.sh b/script/deploy/deployToAllNetworks.sh index 02395ea25..2d8c94398 100755 --- a/script/deploy/deployToAllNetworks.sh +++ b/script/deploy/deployToAllNetworks.sh @@ -20,6 +20,64 @@ fi echo "Selected contract: $SELECTED_CONTRACT" +# Read defaults from foundry.toml (from [profile.default] section) +DEFAULT_SOLC_VERSION=$(awk '/^\[profile\.default\]/{flag=1; next} /^\[/{flag=0} flag && /^solc_version/{gsub(/['\''"]/, "", $3); print $3; exit}' foundry.toml) +DEFAULT_EVM_VERSION=$(awk '/^\[profile\.default\]/{flag=1; next} /^\[/{flag=0} flag && /^evm_version/{gsub(/['\''"]/, "", $3); print $3; exit}' foundry.toml) + +# Fetch solc versions from GitHub API (0.8.17 to latest) +echo "Fetching available Solidity compiler versions..." +SOLC_VERSIONS=$(curl -s "https://api.github.com/repos/ethereum/solc-js/tags" | \ + jq -r '.[].name' | \ + grep -E '^v0\.(8\.(1[7-9]|[2-9][0-9])|9\.)' | \ + sed 's/^v//' | \ + sort -V -r) + +# Ensure the foundry.toml default is in the list +if ! echo "$SOLC_VERSIONS" | grep -q "^$DEFAULT_SOLC_VERSION$"; then + SOLC_VERSIONS="$DEFAULT_SOLC_VERSION +$SOLC_VERSIONS" +fi + +# Check if we got versions +if [ -z "$SOLC_VERSIONS" ]; then + echo "Failed to fetch Solidity versions. Using foundry.toml default: $DEFAULT_SOLC_VERSION" + SOLC_VERSIONS="$DEFAULT_SOLC_VERSION" +fi + +# Use gum to select Solidity compiler version +SELECTED_SOLC_VERSION=$(echo "$SOLC_VERSIONS" | gum choose \ + --limit=1 \ + --select-if-one \ + --selected="$DEFAULT_SOLC_VERSION" \ + --header="Select Solidity compiler version:") + +# Check if a solc version was selected +if [ -z "$SELECTED_SOLC_VERSION" ]; then + echo "No Solidity version selected. Exiting." + exit 1 +fi + +echo "Selected Solidity version: $SELECTED_SOLC_VERSION" + +# Use gum to select EVM version +EVM_VERSIONS="shanghai +london +cancun" + +SELECTED_EVM_VERSION=$(echo "$EVM_VERSIONS" | gum choose \ + --limit=1 \ + --select-if-one \ + --selected="$DEFAULT_EVM_VERSION" \ + --header="Select EVM version:") + +# Check if an EVM version was selected +if [ -z "$SELECTED_EVM_VERSION" ]; then + echo "No EVM version selected. Exiting." + exit 1 +fi + +echo "Selected EVM version: $SELECTED_EVM_VERSION" + # Extract network names from config/networks.json, filtering out zkEVM networks NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) @@ -38,5 +96,5 @@ fi echo "Selected networks: $SELECTED_NETWORKS" -# Run dagger command with selected contract and networks -dagger -c "deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY | export ./deployments" +# Run dagger command with selected contract, networks, EVM version, and Solidity version +dagger -c "deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY $SELECTED_EVM_VERSION $SELECTED_SOLC_VERSION | export ./deployments" From 0e9c728153f89470c51b795619e0930c91691c73 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 13:49:01 +0300 Subject: [PATCH 21/37] add --no-select-all --- script/deploy/deployToAllNetworks.sh | 37 ++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/script/deploy/deployToAllNetworks.sh b/script/deploy/deployToAllNetworks.sh index 2d8c94398..6d5438e02 100755 --- a/script/deploy/deployToAllNetworks.sh +++ b/script/deploy/deployToAllNetworks.sh @@ -1,5 +1,21 @@ #!/bin/bash +# Parse command line arguments +SELECT_ALL_NETWORKS=true +while [[ $# -gt 0 ]]; do + case $1 in + --no-select-all) + SELECT_ALL_NETWORKS=false + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--no-select-all]" + exit 1 + ;; + esac +done + source .env source script/config.sh @@ -81,12 +97,19 @@ echo "Selected EVM version: $SELECTED_EVM_VERSION" # Extract network names from config/networks.json, filtering out zkEVM networks NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) -# Use gum to select networks with all networks pre-selected -SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ - --no-limit \ - --selected="*" \ - --header="Select networks to deploy to:" \ - --output-delimiter=",") +# Use gum to select networks (conditionally pre-select all) +if [ "$SELECT_ALL_NETWORKS" = true ]; then + SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ + --no-limit \ + --selected="*" \ + --header="Select networks to deploy to:" \ + --output-delimiter=",") +else + SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ + --no-limit \ + --header="Select networks to deploy to:" \ + --output-delimiter=",") +fi # Check if any networks were selected if [ -z "$SELECTED_NETWORKS" ]; then @@ -97,4 +120,4 @@ fi echo "Selected networks: $SELECTED_NETWORKS" # Run dagger command with selected contract, networks, EVM version, and Solidity version -dagger -c "deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY $SELECTED_EVM_VERSION $SELECTED_SOLC_VERSION | export ./deployments" +dagger -c "deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY --evm-version=$SELECTED_EVM_VERSION --solc-version=$SELECTED_SOLC_VERSION | export ./deployments" From dedcfc79ea09ad06ee22642c7695f44d4fd60f2c Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 14:29:30 +0300 Subject: [PATCH 22/37] specify network --- script/deploy/deployToAllNetworks.sh | 81 ++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/script/deploy/deployToAllNetworks.sh b/script/deploy/deployToAllNetworks.sh index 6d5438e02..07e64ae1b 100755 --- a/script/deploy/deployToAllNetworks.sh +++ b/script/deploy/deployToAllNetworks.sh @@ -2,15 +2,28 @@ # Parse command line arguments SELECT_ALL_NETWORKS=true +NETWORKS_ARG="" while [[ $# -gt 0 ]]; do case $1 in --no-select-all) + if [ -n "$NETWORKS_ARG" ]; then + echo "Error: --no-select-all and --networks are mutually exclusive" + exit 1 + fi SELECT_ALL_NETWORKS=false shift ;; + --networks) + if [ "$SELECT_ALL_NETWORKS" = false ]; then + echo "Error: --no-select-all and --networks are mutually exclusive" + exit 1 + fi + NETWORKS_ARG="$2" + shift 2 + ;; *) echo "Unknown option: $1" - echo "Usage: $0 [--no-select-all]" + echo "Usage: $0 [--no-select-all | --networks network1,network2,...]" exit 1 ;; esac @@ -94,27 +107,53 @@ fi echo "Selected EVM version: $SELECTED_EVM_VERSION" -# Extract network names from config/networks.json, filtering out zkEVM networks -NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) - -# Use gum to select networks (conditionally pre-select all) -if [ "$SELECT_ALL_NETWORKS" = true ]; then - SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ - --no-limit \ - --selected="*" \ - --header="Select networks to deploy to:" \ - --output-delimiter=",") +# Handle network selection +if [ -n "$NETWORKS_ARG" ]; then + # Validate provided networks against available networks + AVAILABLE_NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) + + # Convert comma-separated list to array and validate each network + IFS=',' read -ra NETWORK_ARRAY <<< "$NETWORKS_ARG" + INVALID_NETWORKS=() + + for network in "${NETWORK_ARRAY[@]}"; do + if ! echo "$AVAILABLE_NETWORKS" | grep -q "^$network$"; then + INVALID_NETWORKS+=("$network") + fi + done + + if [ ${#INVALID_NETWORKS[@]} -gt 0 ]; then + echo "Error: Invalid networks specified: ${INVALID_NETWORKS[*]}" + echo "Available networks:" + echo "$AVAILABLE_NETWORKS" + exit 1 + fi + + SELECTED_NETWORKS="$NETWORKS_ARG" + echo "Using provided networks: $SELECTED_NETWORKS" else - SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ - --no-limit \ - --header="Select networks to deploy to:" \ - --output-delimiter=",") -fi - -# Check if any networks were selected -if [ -z "$SELECTED_NETWORKS" ]; then - echo "No networks selected. Exiting." - exit 1 + # Extract network names from config/networks.json, filtering out zkEVM networks + NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) + + # Use gum to select networks (conditionally pre-select all) + if [ "$SELECT_ALL_NETWORKS" = true ]; then + SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ + --no-limit \ + --selected="*" \ + --header="Select networks to deploy to:" \ + --output-delimiter=",") + else + SELECTED_NETWORKS=$(echo "$NETWORKS" | gum choose \ + --no-limit \ + --header="Select networks to deploy to:" \ + --output-delimiter=",") + fi + + # Check if any networks were selected + if [ -z "$SELECTED_NETWORKS" ]; then + echo "No networks selected. Exiting." + exit 1 + fi fi echo "Selected networks: $SELECTED_NETWORKS" From b75007bfad9620e248c0626c0c085e5655720e08 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 16:07:30 +0300 Subject: [PATCH 23/37] proposals --- .dagger/src/index.ts | 284 ++++++++++++++++++++++-- deployments/_deployments_log_file.json | 18 +- deployments/arbitrum.json | 2 +- deployments/failed_deployments_log.json | 53 +++++ script/deploy/deployToAllNetworks.sh | 40 +++- 5 files changed, 373 insertions(+), 24 deletions(-) create mode 100644 deployments/failed_deployments_log.json diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index c43b43c86..d6aae84ae 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -47,6 +47,8 @@ export class LifiContracts { .withFile('/workspace/foundry.toml', source.file('foundry.toml')) .withFile('/workspace/remappings.txt', source.file('remappings.txt')) .withFile('/workspace/.env', source.file('.env')) + .withFile('/workspace/package.json', source.file('package.json')) + .withFile('/workspace/bun.lock', source.file('bun.lock')) .withWorkdir('/workspace') .withUser('root') .withExec([ @@ -56,14 +58,14 @@ export class LifiContracts { '/workspace/cache', '/workspace/broadcast', ]) - .withExec([ - 'chown', - 'foundry:foundry', - '/workspace/out', - '/workspace/cache', - '/workspace/broadcast', - ]) + .withExec(['chown', '-R', 'foundry:foundry', '/workspace']) + // Install Node.js, bun, and jq + .withExec(['apt-get', 'update']) + .withExec(['apt-get', 'install', '-y', 'nodejs', 'npm', 'jq']) + .withExec(['npm', 'install', '-g', 'bun']) .withUser('foundry') + // Install dependencies + .withExec(['bun', 'install', '--ignore-scripts']) .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') // Read defaults from foundry.toml if versions not provided @@ -155,11 +157,13 @@ export class LifiContracts { // Build the project first with the same versions as deployment const builtContainer = await this.buildProject(source, solc, evm) - // Mount the deployments directory to the built container - const containerWithDeployments = builtContainer.withMountedDirectory( - '/workspace/deployments', - source.directory('deployments') - ) + // Mount the deployments and config directories to the built container + const containerWithDeployments = builtContainer + .withMountedDirectory( + '/workspace/deployments', + source.directory('deployments') + ) + .withMountedDirectory('/workspace/config', source.directory('config')) // Build forge script command const forgeArgs = [ @@ -363,6 +367,214 @@ export class LifiContracts { ) } + /** + * Check if a contract is a facet (exists in src/Facets directory) + */ + private async checkIfFacetExists( + source: Directory, + contractName: string + ): Promise { + try { + const facetPath = `src/Facets/${contractName}.sol` + await source.file(facetPath).id() + return true + } catch (error) { + return false + } + } + + /** + * Update diamond with a facet using TypeScript-heavy approach + */ + @func() + async updateFacet( + source: Directory, + contractName: string, + network: string, + privateKey: Secret, + evmVersion?: string, + solcVersion?: string, + environment: string = 'staging', + safeSignerPrivateKey?: Secret + ): Promise { + console.log( + `🔄 Starting diamond update for ${contractName} on ${network} (${environment})` + ) + + try { + // 1. Check if update script exists + const updateScriptPath = `script/deploy/facets/Update${contractName}.s.sol` + try { + await source.file(updateScriptPath).id() + } catch (error) { + throw new Error(`Update script not found: ${updateScriptPath}`) + } + + // 2. Read diamond address from deployment file + const fileSuffix = environment === 'production' ? '' : '.staging' + const deploymentFile = `deployments/${network}${fileSuffix}.json` + + let deploymentContent: string + try { + deploymentContent = await source.file(deploymentFile).contents() + } catch (error) { + throw new Error(`Deployment file not found: ${deploymentFile}`) + } + + const deployments = JSON.parse(deploymentContent) + const diamondAddress = deployments.LiFiDiamond + + if (!diamondAddress) { + throw new Error(`LiFiDiamond address not found in ${deploymentFile}`) + } + + console.log(`📍 Found LiFiDiamond at ${diamondAddress}`) + + // 3. Read network config for RPC URL (needed for Safe proposals) + const networkConfigContent = await source + .file('config/networks.json') + .contents() + const networkConfig = JSON.parse(networkConfigContent) + const rpcUrl = + networkConfig[network]?.rpcUrl || networkConfig[network]?.rpc + + if (!rpcUrl && environment === 'production') { + throw new Error(`RPC URL not found for network ${network}`) + } + + // 4. Build project with same versions as deployment + const builtContainer = await this.buildProject( + source, + solcVersion, + evmVersion + ) + + // 5. Setup container with environment variables + let container = builtContainer + .withEnvVariable('NETWORK', network) + .withEnvVariable('FILE_SUFFIX', fileSuffix) + .withEnvVariable('USE_DEF_DIAMOND', 'true') + .withSecretVariable('PRIVATE_KEY', privateKey) + .withMountedDirectory( + '/workspace/deployments', + source.directory('deployments') + ) + + // 6. Build forge command based on environment + const forgeArgs = [ + 'forge', + 'script', + updateScriptPath, + '-f', + network, + '--json', + '-vvvv', + '--legacy', + ] + + if (environment === 'staging') { + forgeArgs.push('--broadcast') + console.log(`🚀 Executing direct diamond update for staging...`) + } else { + forgeArgs.push('--skip-simulation') + container = container.withEnvVariable('NO_BROADCAST', 'true') + console.log(`📋 Generating Safe proposal for production...`) + } + + // Add version flags if provided + if (solcVersion) { + forgeArgs.push('--use', solcVersion) + } + if (evmVersion) { + forgeArgs.push('--evm-version', evmVersion) + } + + // 7. Execute forge script + const result = await container.withExec(forgeArgs).stdout() + console.log(`✅ Forge script executed successfully`) + + // 8. Parse and validate forge output + let forgeOutput: any + try { + // Extract JSON from output (sometimes has extra text) + const jsonMatch = result.match(/\{"logs":.*/) + const cleanData = jsonMatch ? jsonMatch[0] : result + forgeOutput = JSON.parse(cleanData) + } catch (error) { + throw new Error(`Failed to parse forge output: ${error}`) + } + + if (environment === 'production') { + // 9. Handle production: Create Safe proposal + const facetCut = forgeOutput.returns?.cutData?.value + if (!facetCut || facetCut === '0x') { + throw new Error('No facet cut data generated for Safe proposal') + } + + console.log(`📤 Creating Safe proposal with facet cut data...`) + await this.proposeFacetCutToSafe( + container, + diamondAddress, + facetCut, + network, + rpcUrl, + safeSignerPrivateKey + ) + console.log(`✅ Safe proposal created successfully`) + } else { + // 10. Handle staging: Validate direct update + const facets = forgeOutput.returns?.facets?.value + if (!facets || facets === '{}') { + throw new Error('Facet update failed - no facets returned') + } + console.log(`✅ Diamond updated directly with new facet`) + } + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error) + console.error(`❌ Diamond update failed: ${errorMessage}`) + throw new Error(`Diamond update failed: ${errorMessage}`) + } + } + + /** + * Create Safe proposal for facet cut (production deployments) + */ + private async proposeFacetCutToSafe( + container: Container, + diamondAddress: string, + facetCut: string, + network: string, + rpcUrl: string, + safeSignerPrivateKey?: Secret + ): Promise { + // Add Safe signer private key as secret if provided + let containerWithSafe = container + if (safeSignerPrivateKey) { + containerWithSafe = container.withSecretVariable( + 'SAFE_SIGNER_PRIVATE_KEY', + safeSignerPrivateKey + ) + } + + const safeArgs = [ + 'bun', + 'script/deploy/safe/propose-to-safe.ts', + '--to', + diamondAddress, + '--calldata', + facetCut, + '--network', + network, + '--rpcUrl', + rpcUrl, + '--privateKey', + '$SAFE_SIGNER_PRIVATE_KEY', + ] + + await containerWithSafe.withExec(safeArgs).stdout() + } + /** * Deploy a smart contract with configuration reading from networks.json * @@ -373,6 +585,8 @@ export class LifiContracts { * @param environment - Deployment environment ("staging" or "production", defaults to "production") * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") * @param solcVersion - Solidity compiler version (e.g., "0.8.29") + * @param updateDiamond - Whether to update diamond after deployment (for facets only) + * @param safeSignerPrivateKey - Safe signer private key secret (for production diamond updates) * @returns Updated source directory with deployment logs */ @func() @@ -383,7 +597,9 @@ export class LifiContracts { privateKey: Secret, environment?: string, evmVersion?: string, - solcVersion?: string + solcVersion?: string, + updateDiamond?: boolean, + safeSignerPrivateKey?: Secret ): Promise { const env = environment || 'production' @@ -533,6 +749,36 @@ export class LifiContracts { env ) + // Update diamond if requested and contract is a facet + if (updateDiamond) { + const isFacet = await this.checkIfFacetExists(source, contractName) + if (isFacet) { + console.log(`🔄 Updating diamond with ${contractName} facet...`) + try { + await this.updateFacet( + source, + contractName, + network, + privateKey, + evmVersion, + solcVersion, + env, + safeSignerPrivateKey + ) + console.log(`✅ Diamond updated with ${contractName} facet`) + } catch (error) { + console.error( + `❌ Failed to update diamond with ${contractName}: ${error}` + ) + // Don't throw - deployment was successful, only diamond update failed + } + } else { + console.log( + `ℹ️ ${contractName} is not a facet, skipping diamond update` + ) + } + } + // Return the full updated source directory return source } @@ -547,6 +793,8 @@ export class LifiContracts { * @param environment - Deployment environment ("staging" or "production", defaults to "production") * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") * @param solcVersion - Solidity compiler version (e.g., "0.8.29") + * @param updateDiamond - Whether to update diamond after deployment (for facets only) + * @param safeSignerPrivateKey - Safe signer private key secret (for production diamond updates) */ @func() async deployToAllNetworks( @@ -556,7 +804,9 @@ export class LifiContracts { privateKey: Secret, environment?: string, evmVersion?: string, - solcVersion?: string + solcVersion?: string, + updateDiamond?: boolean, + safeSignerPrivateKey?: Secret ): Promise { let updatedSource = source @@ -570,7 +820,9 @@ export class LifiContracts { privateKey, environment, evmVersion, - solcVersion + solcVersion, + updateDiamond, + safeSignerPrivateKey ) console.log(`✅ Successfully deployed ${contractName} to ${network}`) } catch (error) { @@ -920,7 +1172,7 @@ export class LifiContracts { // Log verification details const logContainer = verificationContainer.withExec([ - 'sh', + '/bin/sh', '-c', ` echo "Contract verification completed successfully for ${contractName} at ${contractAddress}" diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 32a0cbc21..aa4bd1a9e 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -27435,6 +27435,22 @@ "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "true" + }, + { + "ADDRESS": "0x23b4c5716850A0aC675b19E1Dad1771109C8Ad54", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-07-08 11:58:17", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000000000000000000000000000000000000000000000", + "SALT": "", + "VERIFIED": "false" + }, + { + "ADDRESS": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-07-08 12:57:24", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000000000000000000000000000000000000000000000", + "SALT": "", + "VERIFIED": "false" } ] }, @@ -35431,4 +35447,4 @@ } } } -} +} \ No newline at end of file diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index 978a15908..f618c78ca 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -44,7 +44,7 @@ "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d", "SquidFacet": "0x5C2C3F56e33F45389aa4e1DA4D3a807A532a910c", "MayanFacet": "0xBd5cf5C53A14a69FFf27Fe8b23e09bF76bA4De58", - "GenericSwapFacetV3": "0x31a9b1835864706Af10103b31Ea2b79bdb995F5F", + "GenericSwapFacetV3": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", "StargateFacetV2": "0x6e378C84e657C57b2a8d183CFf30ee5CC8989b61", "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", "LiFiDEXAggregator": "0xA41c306315e61120C8F50832D84e1EcbE3B564D5", diff --git a/deployments/failed_deployments_log.json b/deployments/failed_deployments_log.json new file mode 100644 index 000000000..d26404f97 --- /dev/null +++ b/deployments/failed_deployments_log.json @@ -0,0 +1,53 @@ +{ + "GenericSwapFacetV3": { + "arbitrum": { + "production": [ + { + "TIMESTAMP": "2025-07-08 11:56:21", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:12:34", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:16:22", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:18:48", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:25:47", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:31:59", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:35:30", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:37:55", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + }, + { + "TIMESTAMP": "2025-07-08 12:39:49", + "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", + "ENVIRONMENT": "production" + } + ] + } + } +} \ No newline at end of file diff --git a/script/deploy/deployToAllNetworks.sh b/script/deploy/deployToAllNetworks.sh index 07e64ae1b..b6a3e0ff3 100755 --- a/script/deploy/deployToAllNetworks.sh +++ b/script/deploy/deployToAllNetworks.sh @@ -3,6 +3,7 @@ # Parse command line arguments SELECT_ALL_NETWORKS=true NETWORKS_ARG="" +UPDATE_DIAMOND=false while [[ $# -gt 0 ]]; do case $1 in --no-select-all) @@ -21,9 +22,13 @@ while [[ $# -gt 0 ]]; do NETWORKS_ARG="$2" shift 2 ;; + --update-diamond) + UPDATE_DIAMOND=true + shift + ;; *) echo "Unknown option: $1" - echo "Usage: $0 [--no-select-all | --networks network1,network2,...]" + echo "Usage: $0 [--no-select-all | --networks network1,network2,...] [--update-diamond]" exit 1 ;; esac @@ -35,11 +40,13 @@ source script/config.sh # Extract contract names from script/deploy/facets/ directory CONTRACTS=$(find script/deploy/facets/ -name "Deploy*.s.sol" -exec basename {} \; | sed 's/^Deploy//; s/\.s\.sol$//' | sort) -# Use gum to select a single contract -SELECTED_CONTRACT=$(echo "$CONTRACTS" | gum choose \ +# Use gum filter to select a single contract with fuzzy search +SELECTED_CONTRACT=$(echo "$CONTRACTS" | gum filter \ --limit=1 \ --select-if-one \ - --header="Select contract to deploy:") + --header="Select contract to deploy:" \ + --placeholder="Type to filter contracts..." \ + --fuzzy) # Check if a contract was selected if [ -z "$SELECTED_CONTRACT" ]; then @@ -158,5 +165,26 @@ fi echo "Selected networks: $SELECTED_NETWORKS" -# Run dagger command with selected contract, networks, EVM version, and Solidity version -dagger -c "deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY --evm-version=$SELECTED_EVM_VERSION --solc-version=$SELECTED_SOLC_VERSION | export ./deployments" +# Ask about diamond update if not specified via flag +if [ "$UPDATE_DIAMOND" = false ]; then + UPDATE_DIAMOND_SELECTION=$(echo -e "no\nyes" | gum choose \ + --limit=1 \ + --header="Update diamond after deployment (for facets only)?") + + if [ "$UPDATE_DIAMOND_SELECTION" = "yes" ]; then + UPDATE_DIAMOND=true + fi +fi + +echo "Update diamond: $UPDATE_DIAMOND" + +# Build dagger command with selected options +DAGGER_CMD="deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY --evm-version=$SELECTED_EVM_VERSION --solc-version=$SELECTED_SOLC_VERSION" + +# Add update-diamond flag if requested +if [ "$UPDATE_DIAMOND" = true ]; then + DAGGER_CMD="$DAGGER_CMD --update-diamond=true --safe-signer-private-key=env:SAFE_SIGNER_PRIVATE_KEY" +fi + +# Run dagger command +dagger -c "$DAGGER_CMD | export ./deployments" From c23cbc63a1af5fa4e79cfeb54d29b2763e98373e Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 16:13:16 +0300 Subject: [PATCH 24/37] fix proposals script --- .dagger/src/index.ts | 16 ++++------------ deployments/_deployments_log_file.json | 18 +----------------- deployments/arbitrum.json | 2 +- 3 files changed, 6 insertions(+), 30 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index d6aae84ae..36cba87b3 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -459,6 +459,7 @@ export class LifiContracts { '/workspace/deployments', source.directory('deployments') ) + .withMountedDirectory('/workspace/config', source.directory('config')) // 6. Build forge command based on environment const forgeArgs = [ @@ -558,18 +559,9 @@ export class LifiContracts { } const safeArgs = [ - 'bun', - 'script/deploy/safe/propose-to-safe.ts', - '--to', - diamondAddress, - '--calldata', - facetCut, - '--network', - network, - '--rpcUrl', - rpcUrl, - '--privateKey', - '$SAFE_SIGNER_PRIVATE_KEY', + '/bin/sh', + '-c', + `bun script/deploy/safe/propose-to-safe.ts --to "${diamondAddress}" --calldata "${facetCut}" --network "${network}" --rpcUrl "${rpcUrl}" --privateKey "$SAFE_SIGNER_PRIVATE_KEY"`, ] await containerWithSafe.withExec(safeArgs).stdout() diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index aa4bd1a9e..32a0cbc21 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -27435,22 +27435,6 @@ "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "true" - }, - { - "ADDRESS": "0x23b4c5716850A0aC675b19E1Dad1771109C8Ad54", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-07-08 11:58:17", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000000000000000000000000000000000000000000000", - "SALT": "", - "VERIFIED": "false" - }, - { - "ADDRESS": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-07-08 12:57:24", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000000000000000000000000000000000000000000000", - "SALT": "", - "VERIFIED": "false" } ] }, @@ -35447,4 +35431,4 @@ } } } -} \ No newline at end of file +} diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index f618c78ca..978a15908 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -44,7 +44,7 @@ "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d", "SquidFacet": "0x5C2C3F56e33F45389aa4e1DA4D3a807A532a910c", "MayanFacet": "0xBd5cf5C53A14a69FFf27Fe8b23e09bF76bA4De58", - "GenericSwapFacetV3": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", + "GenericSwapFacetV3": "0x31a9b1835864706Af10103b31Ea2b79bdb995F5F", "StargateFacetV2": "0x6e378C84e657C57b2a8d183CFf30ee5CC8989b61", "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", "LiFiDEXAggregator": "0xA41c306315e61120C8F50832D84e1EcbE3B564D5", From 94c1cdc7aacd37b767c15bf880ea0d7a55449d20 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 16:34:32 +0300 Subject: [PATCH 25/37] cleanup --- .dagger/src/index.ts | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 36cba87b3..2d547132e 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -158,7 +158,7 @@ export class LifiContracts { const builtContainer = await this.buildProject(source, solc, evm) // Mount the deployments and config directories to the built container - const containerWithDeployments = builtContainer + let containerWithDeployments = builtContainer .withMountedDirectory( '/workspace/deployments', source.directory('deployments') @@ -194,7 +194,7 @@ export class LifiContracts { forgeArgs.push('--gas-estimate-multiplier', gasMultiplier) // Set required environment variables that the deployment scripts expect - let deployContainer = containerWithDeployments + containerWithDeployments = containerWithDeployments .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') .withEnvVariable('DEPLOYSALT', deploySalt) .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) @@ -209,21 +209,21 @@ export class LifiContracts { // Add optional environment variables if provided if (defaultDiamondAddressDeploysalt) { - deployContainer = deployContainer.withEnvVariable( + containerWithDeployments = containerWithDeployments.withEnvVariable( 'DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT', defaultDiamondAddressDeploysalt ) } if (diamondType) { - deployContainer = deployContainer.withEnvVariable( + containerWithDeployments = containerWithDeployments.withEnvVariable( 'DIAMOND_TYPE', diamondType ) } // Execute the forge script command - return deployContainer.withExec(forgeArgs) + return containerWithDeployments.withExec(forgeArgs) } /** @@ -443,14 +443,14 @@ export class LifiContracts { } // 4. Build project with same versions as deployment - const builtContainer = await this.buildProject( + let builtContainer = await this.buildProject( source, solcVersion, evmVersion ) // 5. Setup container with environment variables - let container = builtContainer + builtContainer = builtContainer .withEnvVariable('NETWORK', network) .withEnvVariable('FILE_SUFFIX', fileSuffix) .withEnvVariable('USE_DEF_DIAMOND', 'true') @@ -478,7 +478,7 @@ export class LifiContracts { console.log(`🚀 Executing direct diamond update for staging...`) } else { forgeArgs.push('--skip-simulation') - container = container.withEnvVariable('NO_BROADCAST', 'true') + builtContainer = builtContainer.withEnvVariable('NO_BROADCAST', 'true') console.log(`📋 Generating Safe proposal for production...`) } @@ -491,7 +491,7 @@ export class LifiContracts { } // 7. Execute forge script - const result = await container.withExec(forgeArgs).stdout() + const result = await builtContainer.withExec(forgeArgs).stdout() console.log(`✅ Forge script executed successfully`) // 8. Parse and validate forge output @@ -514,7 +514,7 @@ export class LifiContracts { console.log(`📤 Creating Safe proposal with facet cut data...`) await this.proposeFacetCutToSafe( - container, + builtContainer, diamondAddress, facetCut, network, @@ -550,9 +550,8 @@ export class LifiContracts { safeSignerPrivateKey?: Secret ): Promise { // Add Safe signer private key as secret if provided - let containerWithSafe = container if (safeSignerPrivateKey) { - containerWithSafe = container.withSecretVariable( + container = container.withSecretVariable( 'SAFE_SIGNER_PRIVATE_KEY', safeSignerPrivateKey ) @@ -564,7 +563,7 @@ export class LifiContracts { `bun script/deploy/safe/propose-to-safe.ts --to "${diamondAddress}" --calldata "${facetCut}" --network "${network}" --rpcUrl "${rpcUrl}" --privateKey "$SAFE_SIGNER_PRIVATE_KEY"`, ] - await containerWithSafe.withExec(safeArgs).stdout() + await container.withExec(safeArgs).stdout() } /** @@ -1118,7 +1117,7 @@ export class LifiContracts { const updatedLogs = JSON.stringify(currentLogs, null, 2) // Update the source directory with new deployment files - let updatedSource = source + source = source .withNewFile(`deployments/${deploymentFileName}`, updatedDeployments) .withNewFile(`deployments/${logFileName}`, updatedLogs) @@ -1128,7 +1127,7 @@ export class LifiContracts { ) console.log(`Updated master log file: deployments/${logFileName}`) - return updatedSource + return source } /** From 3a770b5d478363b7f9342ca32f29f50efcf2b11e Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 16:35:00 +0300 Subject: [PATCH 26/37] cleanup --- deployments/failed_deployments_log.json | 53 ------------------------- 1 file changed, 53 deletions(-) delete mode 100644 deployments/failed_deployments_log.json diff --git a/deployments/failed_deployments_log.json b/deployments/failed_deployments_log.json deleted file mode 100644 index d26404f97..000000000 --- a/deployments/failed_deployments_log.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "GenericSwapFacetV3": { - "arbitrum": { - "production": [ - { - "TIMESTAMP": "2025-07-08 11:56:21", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:12:34", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:16:22", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:18:48", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:25:47", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:31:59", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:35:30", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:37:55", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - }, - { - "TIMESTAMP": "2025-07-08 12:39:49", - "ERROR_MESSAGE": "UnknownDaggerError: Encountered an unknown error while requesting data via graphql\nStack: UnknownDaggerError: Encountered an unknown error while requesting data via graphql\n at new DaggerSDKError (/src/.dagger/sdk/core.js:61780:7)\n at new UnknownDaggerError (/src/.dagger/sdk/core.js:61820:7)\n at (/src/.dagger/sdk/core.js:86274:15)\n at processTicksAndRejections (native:7:39)", - "ENVIRONMENT": "production" - } - ] - } - } -} \ No newline at end of file From b4b10ab1c1bac0ebdc4e3b07f6f1032973c9a70f Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 16:42:19 +0300 Subject: [PATCH 27/37] more cleanup --- .dagger/src/index.ts | 7 ++++++- deployments/_deployments_log_file.json | 10 +++++++++- deployments/arbitrum.json | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 2d547132e..426b2bc47 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -560,7 +560,12 @@ export class LifiContracts { const safeArgs = [ '/bin/sh', '-c', - `bun script/deploy/safe/propose-to-safe.ts --to "${diamondAddress}" --calldata "${facetCut}" --network "${network}" --rpcUrl "${rpcUrl}" --privateKey "$SAFE_SIGNER_PRIVATE_KEY"`, + 'bun script/deploy/safe/propose-to-safe.ts --to "$1" --calldata "$2" --network "$3" --rpcUrl "$4" --privateKey "$SAFE_SIGNER_PRIVATE_KEY"', + '--', + diamondAddress, + facetCut, + network, + rpcUrl, ] await container.withExec(safeArgs).stdout() diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 32a0cbc21..7da5ac6c3 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -27435,6 +27435,14 @@ "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "true" + }, + { + "ADDRESS": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", + "OPTIMIZER_RUNS": "1000000", + "TIMESTAMP": "2025-07-08 13:41:29", + "CONSTRUCTOR_ARGS": "0x0000000000000000000000000000000000000000000000000000000000000000", + "SALT": "", + "VERIFIED": "false" } ] }, @@ -35431,4 +35439,4 @@ } } } -} +} \ No newline at end of file diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index 978a15908..f618c78ca 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -44,7 +44,7 @@ "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d", "SquidFacet": "0x5C2C3F56e33F45389aa4e1DA4D3a807A532a910c", "MayanFacet": "0xBd5cf5C53A14a69FFf27Fe8b23e09bF76bA4De58", - "GenericSwapFacetV3": "0x31a9b1835864706Af10103b31Ea2b79bdb995F5F", + "GenericSwapFacetV3": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", "StargateFacetV2": "0x6e378C84e657C57b2a8d183CFf30ee5CC8989b61", "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", "LiFiDEXAggregator": "0xA41c306315e61120C8F50832D84e1EcbE3B564D5", From d9d1c38f7e4e2baf1ba914969808de20e5e22855 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 16:51:47 +0300 Subject: [PATCH 28/37] more cleanup --- deployments/_deployments_log_file.json | 10 +--------- deployments/arbitrum.json | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/deployments/_deployments_log_file.json b/deployments/_deployments_log_file.json index 7da5ac6c3..32a0cbc21 100644 --- a/deployments/_deployments_log_file.json +++ b/deployments/_deployments_log_file.json @@ -27435,14 +27435,6 @@ "CONSTRUCTOR_ARGS": "0x", "SALT": "", "VERIFIED": "true" - }, - { - "ADDRESS": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", - "OPTIMIZER_RUNS": "1000000", - "TIMESTAMP": "2025-07-08 13:41:29", - "CONSTRUCTOR_ARGS": "0x0000000000000000000000000000000000000000000000000000000000000000", - "SALT": "", - "VERIFIED": "false" } ] }, @@ -35439,4 +35431,4 @@ } } } -} \ No newline at end of file +} diff --git a/deployments/arbitrum.json b/deployments/arbitrum.json index f618c78ca..978a15908 100644 --- a/deployments/arbitrum.json +++ b/deployments/arbitrum.json @@ -44,7 +44,7 @@ "TokenWrapper": "0x5215E9fd223BC909083fbdB2860213873046e45d", "SquidFacet": "0x5C2C3F56e33F45389aa4e1DA4D3a807A532a910c", "MayanFacet": "0xBd5cf5C53A14a69FFf27Fe8b23e09bF76bA4De58", - "GenericSwapFacetV3": "0x7b863dcD8bF8a61A3f0b6D98dC6540b8ADF67E7c", + "GenericSwapFacetV3": "0x31a9b1835864706Af10103b31Ea2b79bdb995F5F", "StargateFacetV2": "0x6e378C84e657C57b2a8d183CFf30ee5CC8989b61", "ReceiverStargateV2": "0x1493e7B8d4DfADe0a178dAD9335470337A3a219A", "LiFiDEXAggregator": "0xA41c306315e61120C8F50832D84e1EcbE3B564D5", From af4f9034d65ea49eeaf736f43db44146f6f8f495 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 17:03:28 +0300 Subject: [PATCH 29/37] even more cleanup --- .dagger/src/index.ts | 309 ++++++++++++++++++++++++------------------- 1 file changed, 176 insertions(+), 133 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 426b2bc47..4a3886ba2 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -22,9 +22,92 @@ interface NetworkConfig { explorerApiUrl: string create3Factory: string } - @object() export class LifiContracts { + /// Utility Methods /// + + /** + * Parse JSON string with explicit error handling + */ + private parseJson(jsonString: string, context: string): any { + try { + const trimmed = jsonString.trim() + if (!trimmed) { + throw new Error(`Empty JSON content in ${context}`) + } + return JSON.parse(trimmed) + } catch (e) { + throw new Error(`Failed to parse JSON in ${context}: ${e}`) + } + } + + /** + * Read file with explicit error handling + */ + private async readFile(source: Directory, filePath: string): Promise { + try { + return await source.file(filePath).contents() + } catch (e) { + throw new Error(`Failed to read required file: ${filePath}`) + } + } + + /** + * Get network configuration with validation + */ + private async getNetworkConfig( + source: Directory, + network: string + ): Promise { + const networks = await this.readAndParseJson(source, 'config/networks.json') + + if (!networks[network]) { + throw new Error(`Network ${network} not found in networks.json`) + } + + return networks[network] as NetworkConfig + } + + /** + * Read and parse JSON file in one operation + */ + private async readAndParseJson( + source: Directory, + filePath: string + ): Promise { + const content = await this.readFile(source, filePath) + return this.parseJson(content, filePath) + } + + /** + * Ensure nested log structure exists + */ + private ensureLogStructure( + logs: any, + contractName: string, + network: string, + environment: string, + version: string + ): void { + if (!logs[contractName]) logs[contractName] = {} + if (!logs[contractName][network]) logs[contractName][network] = {} + if (!logs[contractName][network][environment]) { + logs[contractName][network][environment] = {} + } + if (!logs[contractName][network][environment][version]) { + logs[contractName][network][environment][version] = [] + } + } + + /** + * Generate timestamp in deployment log format + */ + private generateTimestamp(): string { + return new Date() + .toISOString() + .replace('T', ' ') + .replace(/\.\d{3}Z$/, '') + } /** * Build the Foundry project using forge build * @@ -335,15 +418,7 @@ export class LifiContracts { skipIsVerifiedCheck?: boolean ): Promise { // Read network configuration from networks.json - const networksFile = source.file('config/networks.json') - const networksContent = await networksFile.contents() - const networks = JSON.parse(networksContent) - - if (!networks[network]) { - throw new Error(`Network ${network} not found in networks.json`) - } - - const networkConfig = networks[network] as NetworkConfig + const networkConfig = await this.getNetworkConfig(source, network) // Build the project first to get the same artifacts as deployment const builtContainer = await this.buildProject( @@ -431,12 +506,8 @@ export class LifiContracts { console.log(`📍 Found LiFiDiamond at ${diamondAddress}`) // 3. Read network config for RPC URL (needed for Safe proposals) - const networkConfigContent = await source - .file('config/networks.json') - .contents() - const networkConfig = JSON.parse(networkConfigContent) - const rpcUrl = - networkConfig[network]?.rpcUrl || networkConfig[network]?.rpc + const networkConfig = await this.getNetworkConfig(source, network) + const rpcUrl = networkConfig.rpcUrl if (!rpcUrl && environment === 'production') { throw new Error(`RPC URL not found for network ${network}`) @@ -600,15 +671,7 @@ export class LifiContracts { const env = environment || 'production' // Read network configuration from networks.json - const networksFile = source.file('config/networks.json') - const networksContent = await networksFile.contents() - const networks = JSON.parse(networksContent) - - if (!networks[network]) { - throw new Error(`Network ${network} not found in networks.json`) - } - - const networkConfig = networks[network] as NetworkConfig + const networkConfig = await this.getNetworkConfig(source, network) // Build the project first with foundry.toml defaults (buildProject will read foundry.toml) let builtContainer = await this.buildProject( @@ -874,39 +937,15 @@ export class LifiContracts { const deploymentFileName = `${network}${fileSuffix}.json` const logFileName = '_deployments_log_file.json' - // Read current deployment files from source directory - let currentDeploymentsRaw = '{}' - try { - const deploymentFile = source - .directory('deployments') - .file(deploymentFileName) - currentDeploymentsRaw = await deploymentFile.contents() - } catch (e) { - // File doesn't exist, use empty object - } - - let currentLogsRaw = '{}' - try { - const logFile = source.directory('deployments').file(logFileName) - currentLogsRaw = await logFile.contents() - } catch (e) { - // File doesn't exist, use empty object - } - - // Parse and update deployment data using TypeScript - let currentDeployments: any = {} - try { - currentDeployments = JSON.parse(currentDeploymentsRaw.trim() || '{}') - } catch (e) { - currentDeployments = {} - } - - let currentLogs: any = {} - try { - currentLogs = JSON.parse(currentLogsRaw.trim() || '{}') - } catch (e) { - currentLogs = {} - } + // Read and parse current deployment files + const currentDeployments = await this.readAndParseJson( + source, + `deployments/${deploymentFileName}` + ) + const currentLogs = await this.readAndParseJson( + source, + `deployments/${logFileName}` + ) // Update network-specific deployment file if (currentDeployments[contractName]) { @@ -965,29 +1004,12 @@ export class LifiContracts { ): Promise { const failedLogFileName = 'failed_deployments_log.json' - // Read current failed deployments log - let currentFailedLogsRaw = '{}' - try { - const failedLogFile = source - .directory('deployments') - .file(failedLogFileName) - currentFailedLogsRaw = await failedLogFile.contents() - } catch (e) { - // File doesn't exist, use empty object - } - - // Parse and update failed deployment data - const timestamp = new Date() - .toISOString() - .replace('T', ' ') - .replace(/\.\d{3}Z$/, '') - - let currentFailedLogs: any = {} - try { - currentFailedLogs = JSON.parse(currentFailedLogsRaw.trim() || '{}') - } catch (e) { - currentFailedLogs = {} - } + // Read and parse current failed deployments log + const currentFailedLogs = await this.readAndParseJson( + source, + `deployments/${failedLogFileName}` + ) + const timestamp = this.generateTimestamp() // Create nested structure: contractName -> network -> environment -> array if (!currentFailedLogs[contractName]) { @@ -1024,6 +1046,58 @@ export class LifiContracts { return updatedSource } + /** + * Extract version from contract natspec @custom:version comment + */ + private async extractContractVersion( + source: Directory, + contractName: string + ): Promise { + // Check in src/Facets, src/Periphery, and src/Security directories + const possiblePaths = [ + `src/Facets/${contractName}.sol`, + `src/Periphery/${contractName}.sol`, + `src/Security/${contractName}.sol`, + ] + + for (const contractPath of possiblePaths) { + try { + const contractContent = await source.file(contractPath).contents() + + // Look for @custom:version x.x.x pattern in natspec comments + const versionMatch = contractContent.match( + /@custom:version\s+(\d+\.\d+\.\d+)/ + ) + + if (versionMatch) { + return versionMatch[1] + } + } catch (error) { + // File doesn't exist, continue to next path + continue + } + } + + throw new Error( + `Contract version not found for ${contractName}. Please add @custom:version x.x.x to the contract natspec.` + ) + } + + /** + * Extract optimizer runs from foundry.toml + */ + private async extractOptimizerRuns(source: Directory): Promise { + const foundryToml = await source.file('foundry.toml').contents() + + // Look for optimizer_runs = number pattern + const optimizerMatch = foundryToml.match(/optimizer_runs\s*=\s*(\d+)/) + + if (optimizerMatch) { + return optimizerMatch[1] + } + + throw new Error('optimizer_runs not found in foundry.toml') + } /** * Log deployment details to deployment files */ @@ -1040,65 +1114,34 @@ export class LifiContracts { const deploymentFileName = `${network}${fileSuffix}.json` const logFileName = '_deployments_log_file.json' - // Read current deployment files from source directory or create empty ones - let currentDeploymentsRaw = '{}' - try { - const deploymentFile = source - .directory('deployments') - .file(deploymentFileName) - currentDeploymentsRaw = await deploymentFile.contents() - } catch (e) { - // File doesn't exist, use empty object - } - - let currentLogsRaw = '{}' - try { - const logFile = source.directory('deployments').file(logFileName) - currentLogsRaw = await logFile.contents() - } catch (e) { - // File doesn't exist, use empty object - } + // Extract version and optimizer runs + const version = await this.extractContractVersion(source, contractName) + const optimizerRuns = await this.extractOptimizerRuns(source) - // Parse and update deployment data using TypeScript - const timestamp = new Date() - .toISOString() - .replace('T', ' ') - .replace(/\.\d{3}Z$/, '') - - let currentDeployments: any = {} - try { - currentDeployments = JSON.parse(currentDeploymentsRaw.trim() || '{}') - } catch (e) { - currentDeployments = {} - } + // Read and parse current deployment files + const currentDeployments = await this.readAndParseJson( + source, + `deployments/${deploymentFileName}` + ) + const currentLogs = await this.readAndParseJson( + source, + `deployments/${logFileName}` + ) - let currentLogs: any = {} - try { - currentLogs = JSON.parse(currentLogsRaw.trim() || '{}') - } catch (e) { - currentLogs = {} - } + const timestamp = this.generateTimestamp() // Update deployment data (network-specific file) // For network files, just store the address as a string (matching existing format) currentDeployments[contractName] = contractAddress // Update master log with nested structure: contractName -> network -> environment -> version -> array - if (!currentLogs[contractName]) { - currentLogs[contractName] = {} - } - if (!currentLogs[contractName][network]) { - currentLogs[contractName][network] = {} - } - if (!currentLogs[contractName][network][environment]) { - currentLogs[contractName][network][environment] = {} - } - - // Use version 1.0.0 as default (could be extracted from contract source later) - const version = '1.0.0' - if (!currentLogs[contractName][network][environment][version]) { - currentLogs[contractName][network][environment][version] = [] - } + this.ensureLogStructure( + currentLogs, + contractName, + network, + environment, + version + ) // Remove existing entry for same address if it exists currentLogs[contractName][network][environment][version] = currentLogs[ @@ -1110,7 +1153,7 @@ export class LifiContracts { // Add new deployment entry currentLogs[contractName][network][environment][version].push({ ADDRESS: contractAddress, - OPTIMIZER_RUNS: '1000000', // Default value, could be extracted from build info + OPTIMIZER_RUNS: optimizerRuns, TIMESTAMP: timestamp, CONSTRUCTOR_ARGS: constructorArgs, SALT: deploySalt, From 6cefe36b16a19746482f457b2e7c089e3c84fbc0 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 17:07:17 +0300 Subject: [PATCH 30/37] remove hardcoded fallbacks --- .dagger/src/index.ts | 84 ++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 4a3886ba2..ec061e9d6 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -151,23 +151,10 @@ export class LifiContracts { .withExec(['bun', 'install', '--ignore-scripts']) .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') - // Read defaults from foundry.toml if versions not provided - let finalSolcVersion = solcVersion - let finalEvmVersion = evmVersion - - if (!solcVersion || !evmVersion) { - const foundryToml = await source.file('foundry.toml').contents() - if (!solcVersion) { - const solcMatch = foundryToml.match( - /solc_version\s*=\s*['"]([^'"]+)['"]/ - ) - finalSolcVersion = solcMatch ? solcMatch[1] : '0.8.29' - } - if (!evmVersion) { - const evmMatch = foundryToml.match(/evm_version\s*=\s*['"]([^'"]+)['"]/) - finalEvmVersion = evmMatch ? evmMatch[1] : 'cancun' - } - } + // Read from foundry.toml if versions not provided + const finalSolcVersion = + solcVersion || (await this.extractSolcVersion(source)) + const finalEvmVersion = evmVersion || (await this.extractEvmVersion(source)) // Build forge build command with version parameters const buildArgs = ['forge', 'build'] @@ -234,11 +221,13 @@ export class LifiContracts { const useLegacy = legacy !== false const useSlow = slow !== false const shouldSkipSimulation = skipSimulation === true - // Use provided versions or let buildProject read foundry.toml defaults - const solc = solcVersion || '0.8.29' // fallback only - const evm = evmVersion || 'cancun' // fallback only - // Build the project first with the same versions as deployment - const builtContainer = await this.buildProject(source, solc, evm) + + // Build the project first - buildProject will read foundry.toml if versions not provided + const builtContainer = await this.buildProject( + source, + solcVersion, + evmVersion + ) // Mount the deployments and config directories to the built container let containerWithDeployments = builtContainer @@ -249,18 +238,15 @@ export class LifiContracts { .withMountedDirectory('/workspace/config', source.directory('config')) // Build forge script command - const forgeArgs = [ - 'forge', - 'script', - scriptPath, - '-f', - network, - '--use', - solc, - '--evm-version', - evm, - '--json', - ] + const forgeArgs = ['forge', 'script', scriptPath, '-f', network, '--json'] + + // Add version flags if provided + if (solcVersion) { + forgeArgs.push('--use', solcVersion) + } + if (evmVersion) { + forgeArgs.push('--evm-version', evmVersion) + } // Add verbosity flag only if specified if (verbosity) { @@ -1098,6 +1084,36 @@ export class LifiContracts { throw new Error('optimizer_runs not found in foundry.toml') } + + /** + * Extract solc version from foundry.toml + */ + private async extractSolcVersion(source: Directory): Promise { + const foundryToml = await source.file('foundry.toml').contents() + + const solcMatch = foundryToml.match(/solc_version\s*=\s*['"]([^'"]+)['"]/) + + if (solcMatch) { + return solcMatch[1] + } + + throw new Error('solc_version not found in foundry.toml') + } + + /** + * Extract evm version from foundry.toml + */ + private async extractEvmVersion(source: Directory): Promise { + const foundryToml = await source.file('foundry.toml').contents() + + const evmMatch = foundryToml.match(/evm_version\s*=\s*['"]([^'"]+)['"]/) + + if (evmMatch) { + return evmMatch[1] + } + + throw new Error('evm_version not found in foundry.toml') + } /** * Log deployment details to deployment files */ From ca55cd033b01b8a69438fdde901fbfdd0b8e60e4 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 18:16:14 +0300 Subject: [PATCH 31/37] log salt properly --- .dagger/src/index.ts | 184 ++++++++++++--------------- script/deploy/deployToAllNetworks.sh | 12 +- 2 files changed, 89 insertions(+), 107 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index ec061e9d6..177e158e9 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -121,35 +121,7 @@ export class LifiContracts { solcVersion?: string, evmVersion?: string ): Promise { - let container = dag - .container() - .from('ghcr.io/foundry-rs/foundry:latest') - .withDirectory('/workspace/src', source.directory('src')) - .withDirectory('/workspace/lib', source.directory('lib')) - .withDirectory('/workspace/script', source.directory('script')) - .withFile('/workspace/foundry.toml', source.file('foundry.toml')) - .withFile('/workspace/remappings.txt', source.file('remappings.txt')) - .withFile('/workspace/.env', source.file('.env')) - .withFile('/workspace/package.json', source.file('package.json')) - .withFile('/workspace/bun.lock', source.file('bun.lock')) - .withWorkdir('/workspace') - .withUser('root') - .withExec([ - 'mkdir', - '-p', - '/workspace/out', - '/workspace/cache', - '/workspace/broadcast', - ]) - .withExec(['chown', '-R', 'foundry:foundry', '/workspace']) - // Install Node.js, bun, and jq - .withExec(['apt-get', 'update']) - .withExec(['apt-get', 'install', '-y', 'nodejs', 'npm', 'jq']) - .withExec(['npm', 'install', '-g', 'bun']) - .withUser('foundry') - // Install dependencies - .withExec(['bun', 'install', '--ignore-scripts']) - .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') + // Setup // Read from foundry.toml if versions not provided const finalSolcVersion = @@ -167,9 +139,40 @@ export class LifiContracts { buildArgs.push('--evm-version', finalEvmVersion) } - container = container.withExec(buildArgs) - - return container + // Execute + + return ( + dag + .container() + .from('ghcr.io/foundry-rs/foundry:latest') + .withDirectory('/workspace/src', source.directory('src')) + .withDirectory('/workspace/lib', source.directory('lib')) + .withDirectory('/workspace/script', source.directory('script')) + .withFile('/workspace/foundry.toml', source.file('foundry.toml')) + .withFile('/workspace/remappings.txt', source.file('remappings.txt')) + .withFile('/workspace/.env', source.file('.env')) + .withFile('/workspace/package.json', source.file('package.json')) + .withFile('/workspace/bun.lock', source.file('bun.lock')) + .withWorkdir('/workspace') + .withUser('root') + .withExec([ + 'mkdir', + '-p', + '/workspace/out', + '/workspace/cache', + '/workspace/broadcast', + ]) + .withExec(['chown', '-R', 'foundry:foundry', '/workspace']) + // Install Node.js, bun, and jq + .withExec(['apt-get', 'update']) + .withExec(['apt-get', 'install', '-y', 'nodejs', 'npm', 'jq']) + .withExec(['npm', 'install', '-g', 'bun']) + .withUser('foundry') + // Install dependencies + .withExec(['bun', 'install', '--ignore-scripts']) + .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') + .withExec(buildArgs) + ) } /** @@ -182,6 +185,7 @@ export class LifiContracts { * @param create3FactoryAddress - Address of the CREATE3 factory contract * @param fileSuffix - File suffix for deployment logs (e.g., "staging", "production") * @param privateKey - Private key secret for deployment + * @param originalSalt - Original salt value for logging (before keccak hashing) * @param solcVersion - Solidity compiler version (e.g., "0.8.29") * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") * @param gasEstimateMultiplier - Gas estimate multiplier percentage (default: "130") @@ -203,6 +207,7 @@ export class LifiContracts { create3FactoryAddress: string, fileSuffix: string, privateKey: Secret, + originalSalt: string, solcVersion?: string, evmVersion?: string, gasEstimateMultiplier?: string, @@ -210,10 +215,7 @@ export class LifiContracts { legacy?: boolean, slow?: boolean, skipSimulation?: boolean, - verbosity?: string, - defaultDiamondAddressDeploysalt?: string, - deployToDefaultDiamondAddress?: string, - diamondType?: string + verbosity?: string ): Promise { // Set default values const gasMultiplier = gasEstimateMultiplier || '130' @@ -222,21 +224,6 @@ export class LifiContracts { const useSlow = slow !== false const shouldSkipSimulation = skipSimulation === true - // Build the project first - buildProject will read foundry.toml if versions not provided - const builtContainer = await this.buildProject( - source, - solcVersion, - evmVersion - ) - - // Mount the deployments and config directories to the built container - let containerWithDeployments = builtContainer - .withMountedDirectory( - '/workspace/deployments', - source.directory('deployments') - ) - .withMountedDirectory('/workspace/config', source.directory('config')) - // Build forge script command const forgeArgs = ['forge', 'script', scriptPath, '-f', network, '--json'] @@ -262,37 +249,26 @@ export class LifiContracts { // Add gas estimate multiplier forgeArgs.push('--gas-estimate-multiplier', gasMultiplier) - // Set required environment variables that the deployment scripts expect - containerWithDeployments = containerWithDeployments - .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') - .withEnvVariable('DEPLOYSALT', deploySalt) - .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) - .withEnvVariable('NETWORK', network) - .withEnvVariable('FILE_SUFFIX', fileSuffix) - .withSecretVariable('PRIVATE_KEY', privateKey) - .withEnvVariable('SALT', process.env.SALT || '') - .withEnvVariable( - 'DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS', - deployToDefaultDiamondAddress || 'true' - ) - - // Add optional environment variables if provided - if (defaultDiamondAddressDeploysalt) { - containerWithDeployments = containerWithDeployments.withEnvVariable( - 'DEFAULT_DIAMOND_ADDRESS_DEPLOYSALT', - defaultDiamondAddressDeploysalt - ) - } - - if (diamondType) { - containerWithDeployments = containerWithDeployments.withEnvVariable( - 'DIAMOND_TYPE', - diamondType - ) - } - - // Execute the forge script command - return containerWithDeployments.withExec(forgeArgs) + // Build the project first - buildProject will read foundry.toml if versions not provided + return ( + (await this.buildProject(source, solcVersion, evmVersion)) + // Mount the deployments and config directories to the built container + .withMountedDirectory( + '/workspace/deployments', + source.directory('deployments') + ) + .withMountedDirectory('/workspace/config', source.directory('config')) + // Set required environment variables that the deployment scripts expect + .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') + .withEnvVariable('DEPLOYSALT', deploySalt) + .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) + .withEnvVariable('NETWORK', network) + .withEnvVariable('FILE_SUFFIX', fileSuffix) + .withSecretVariable('PRIVATE_KEY', privateKey) + .withEnvVariable('SALT', originalSalt) + .withEnvVariable('DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS', 'true') + .withExec(forgeArgs) + ) } /** @@ -635,6 +611,7 @@ export class LifiContracts { * @param contractName - Name of the contract to deploy (e.g., "AcrossFacet") * @param network - Target network name (e.g., "arbitrum", "mainnet") * @param privateKey - Private key secret for deployment + * @param salt - Salt value for CREATE3 deployment (optional, defaults to empty string) * @param environment - Deployment environment ("staging" or "production", defaults to "production") * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") * @param solcVersion - Solidity compiler version (e.g., "0.8.29") @@ -648,6 +625,7 @@ export class LifiContracts { contractName: string, network: string, privateKey: Secret, + salt?: string, environment?: string, evmVersion?: string, solcVersion?: string, @@ -655,6 +633,7 @@ export class LifiContracts { safeSignerPrivateKey?: Secret ): Promise { const env = environment || 'production' + const deploymentSalt = salt || '' // Read network configuration from networks.json const networkConfig = await this.getNetworkConfig(source, network) @@ -682,18 +661,15 @@ export class LifiContracts { throw new Error(`No bytecode found for contract ${contractName}`) } - // Get SALT from environment variable (can be empty) - const salt = process.env.SALT || '' - // Validate SALT format if provided (must have even number of digits) - if (salt && salt.length % 2 !== 0) { + if (deploymentSalt && deploymentSalt.length % 2 !== 0) { throw new Error( - 'SALT environment variable has odd number of digits (must be even digits)' + 'SALT parameter has odd number of digits (must be even digits)' ) } // Create salt input by concatenating bytecode and SALT (same as bash: SALT_INPUT="$BYTECODE""$SALT") - const saltInput = bytecode + salt + const saltInput = bytecode + deploymentSalt // Generate DEPLOYSALT using cast keccak (same as bash: DEPLOYSALT=$(cast keccak "$SALT_INPUT")) builtContainer = builtContainer.withExec(['cast', 'keccak', saltInput]) @@ -710,6 +686,7 @@ export class LifiContracts { networkConfig.create3Factory, env === 'production' ? '' : 'staging.', privateKey, // privateKey passed as secret + deploymentSalt, // originalSalt - for logging purposes solcVersion, // solcVersion - use provided version or foundry.toml defaults evmVersion, // evmVersion - use provided version or foundry.toml defaults '130', // gasEstimateMultiplier @@ -717,10 +694,7 @@ export class LifiContracts { true, // legacy true, // slow false, // skipSimulation - undefined, // verbosity - omit by default - undefined, // defaultDiamondAddressDeploysalt - 'true', // deployToDefaultDiamondAddress - undefined // diamondType + undefined // verbosity - omit by default ) // Extract deployment results from the output @@ -768,9 +742,6 @@ export class LifiContracts { ) } - // Use original SALT for logging (can be empty string) - const logSalt = salt - // Update deployment logs and get updated source source = await this.logDeployment( source, @@ -779,7 +750,7 @@ export class LifiContracts { env, contractAddress, constructorArgs, - logSalt + deploymentSalt // Use original SALT parameter, not the computed deploySalt hash ) // Attempt contract verification using the deployment container @@ -835,6 +806,7 @@ export class LifiContracts { * @param contractName - Name of the contract to deploy (e.g., "AcrossFacet") * @param networks - Array of network names to deploy to (e.g., ["arbitrum", "optimism"]) * @param privateKey - Private key secret for deployment + * @param salt - Salt value for CREATE3 deployment (optional, defaults to empty string) * @param environment - Deployment environment ("staging" or "production", defaults to "production") * @param evmVersion - EVM version target (e.g., "cancun", "london", "shanghai") * @param solcVersion - Solidity compiler version (e.g., "0.8.29") @@ -847,6 +819,7 @@ export class LifiContracts { contractName: string, networks: string[], privateKey: Secret, + salt?: string, environment?: string, evmVersion?: string, solcVersion?: string, @@ -863,6 +836,7 @@ export class LifiContracts { contractName, network, privateKey, + salt, environment, evmVersion, solcVersion, @@ -990,11 +964,19 @@ export class LifiContracts { ): Promise { const failedLogFileName = 'failed_deployments_log.json' - // Read and parse current failed deployments log - const currentFailedLogs = await this.readAndParseJson( - source, - `deployments/${failedLogFileName}` - ) + // Read and parse current failed deployments log, create empty object if file doesn't exist + let currentFailedLogs: any = {} + try { + currentFailedLogs = await this.readAndParseJson( + source, + `deployments/${failedLogFileName}` + ) + } catch (error) { + // File doesn't exist, start with empty object + console.log( + `Creating new failed deployments log file: deployments/${failedLogFileName}` + ) + } const timestamp = this.generateTimestamp() // Create nested structure: contractName -> network -> environment -> array diff --git a/script/deploy/deployToAllNetworks.sh b/script/deploy/deployToAllNetworks.sh index b6a3e0ff3..3689dcb1b 100755 --- a/script/deploy/deployToAllNetworks.sh +++ b/script/deploy/deployToAllNetworks.sh @@ -118,24 +118,24 @@ echo "Selected EVM version: $SELECTED_EVM_VERSION" if [ -n "$NETWORKS_ARG" ]; then # Validate provided networks against available networks AVAILABLE_NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) - + # Convert comma-separated list to array and validate each network IFS=',' read -ra NETWORK_ARRAY <<< "$NETWORKS_ARG" INVALID_NETWORKS=() - + for network in "${NETWORK_ARRAY[@]}"; do if ! echo "$AVAILABLE_NETWORKS" | grep -q "^$network$"; then INVALID_NETWORKS+=("$network") fi done - + if [ ${#INVALID_NETWORKS[@]} -gt 0 ]; then echo "Error: Invalid networks specified: ${INVALID_NETWORKS[*]}" echo "Available networks:" echo "$AVAILABLE_NETWORKS" exit 1 fi - + SELECTED_NETWORKS="$NETWORKS_ARG" echo "Using provided networks: $SELECTED_NETWORKS" else @@ -170,7 +170,7 @@ if [ "$UPDATE_DIAMOND" = false ]; then UPDATE_DIAMOND_SELECTION=$(echo -e "no\nyes" | gum choose \ --limit=1 \ --header="Update diamond after deployment (for facets only)?") - + if [ "$UPDATE_DIAMOND_SELECTION" = "yes" ]; then UPDATE_DIAMOND=true fi @@ -179,7 +179,7 @@ fi echo "Update diamond: $UPDATE_DIAMOND" # Build dagger command with selected options -DAGGER_CMD="deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY --evm-version=$SELECTED_EVM_VERSION --solc-version=$SELECTED_SOLC_VERSION" +DAGGER_CMD="deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY --salt=$SALT --evm-version=$SELECTED_EVM_VERSION --solc-version=$SELECTED_SOLC_VERSION" # Add update-diamond flag if requested if [ "$UPDATE_DIAMOND" = true ]; then From e69212a363958371cc0a562630b7f899ec8c6e0a Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Tue, 8 Jul 2025 18:32:06 +0300 Subject: [PATCH 32/37] fix constructor args logging --- .dagger/src/index.ts | 104 +++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 177e158e9..a1bfbd66d 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -99,6 +99,20 @@ export class LifiContracts { } } + /** + * Format constructor args for display - show "0x" for empty args + */ + private formatConstructorArgsForDisplay(constructorArgs: string): string { + if ( + !constructorArgs || + constructorArgs === '0x' || + /^0x0+$/.test(constructorArgs) + ) { + return '0x' + } + return constructorArgs + } + /** * Generate timestamp in deployment log format */ @@ -217,6 +231,8 @@ export class LifiContracts { skipSimulation?: boolean, verbosity?: string ): Promise { + // Setup + // Set default values const gasMultiplier = gasEstimateMultiplier || '130' const shouldBroadcast = broadcast !== false @@ -249,26 +265,23 @@ export class LifiContracts { // Add gas estimate multiplier forgeArgs.push('--gas-estimate-multiplier', gasMultiplier) - // Build the project first - buildProject will read foundry.toml if versions not provided - return ( - (await this.buildProject(source, solcVersion, evmVersion)) - // Mount the deployments and config directories to the built container - .withMountedDirectory( - '/workspace/deployments', - source.directory('deployments') - ) - .withMountedDirectory('/workspace/config', source.directory('config')) - // Set required environment variables that the deployment scripts expect - .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') - .withEnvVariable('DEPLOYSALT', deploySalt) - .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) - .withEnvVariable('NETWORK', network) - .withEnvVariable('FILE_SUFFIX', fileSuffix) - .withSecretVariable('PRIVATE_KEY', privateKey) - .withEnvVariable('SALT', originalSalt) - .withEnvVariable('DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS', 'true') - .withExec(forgeArgs) - ) + // Execute + + return (await this.buildProject(source, solcVersion, evmVersion)) + .withMountedDirectory( + '/workspace/deployments', + source.directory('deployments') + ) + .withMountedDirectory('/workspace/config', source.directory('config')) + .withEnvVariable('FOUNDRY_DISABLE_NIGHTLY_WARNING', 'true') + .withEnvVariable('DEPLOYSALT', deploySalt) + .withEnvVariable('CREATE3_FACTORY_ADDRESS', create3FactoryAddress) + .withEnvVariable('NETWORK', network) + .withEnvVariable('FILE_SUFFIX', fileSuffix) + .withSecretVariable('PRIVATE_KEY', privateKey) + .withEnvVariable('SALT', originalSalt) + .withEnvVariable('DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS', 'true') + .withExec(forgeArgs) } /** @@ -301,19 +314,13 @@ export class LifiContracts { watch?: boolean, skipIsVerifiedCheck?: boolean ): Container { + // Setup + // Set default values const verificationService = verifier || 'etherscan' const shouldWatch = watch !== false const shouldSkipVerifiedCheck = skipIsVerifiedCheck !== false - // Mount the deployments directory to the built container - // Note: The built container already has src, lib, script, foundry.toml, etc. - // We just need to mount the deployments directory for verification - builtContainer = builtContainer.withMountedDirectory( - '/workspace/deployments', - source.directory('deployments') - ) - // Build base verification command const forgeArgs = ['forge', 'verify-contract'] @@ -348,8 +355,14 @@ export class LifiContracts { forgeArgs.push('-e', apiKey) } - // Execute the verification command - return builtContainer.withExec(forgeArgs) + // Execute + + return builtContainer + .withMountedDirectory( + '/workspace/deployments', + source.directory('deployments') + ) + .withExec(forgeArgs) } /** @@ -1153,7 +1166,7 @@ export class LifiContracts { ADDRESS: contractAddress, OPTIMIZER_RUNS: optimizerRuns, TIMESTAMP: timestamp, - CONSTRUCTOR_ARGS: constructorArgs, + CONSTRUCTOR_ARGS: this.formatConstructorArgsForDisplay(constructorArgs), SALT: deploySalt, VERIFIED: 'false', }) @@ -1190,6 +1203,8 @@ export class LifiContracts { environment: string ): Promise { try { + // Setup + // Determine chain ID from network config const chainId = networkConfig.chainId.toString() @@ -1207,17 +1222,6 @@ export class LifiContracts { true // skipIsVerifiedCheck ) - // Log verification details - const logContainer = verificationContainer.withExec([ - '/bin/sh', - '-c', - ` - echo "Contract verification completed successfully for ${contractName} at ${contractAddress}" - echo "Using compiler: ${networkConfig.deployedWithSolcVersion}, EVM: ${networkConfig.deployedWithEvmVersion}" - echo "Constructor args: ${constructorArgs}" - `, - ]) - // Update deployment logs locally await this.updateVerificationLogs( source, @@ -1227,7 +1231,21 @@ export class LifiContracts { contractAddress ) - return logContainer + // Execute + + return verificationContainer.withExec([ + '/bin/sh', + '-c', + ` + echo "Contract verification completed successfully for ${contractName} at ${contractAddress}" + echo "Using compiler: ${ + networkConfig.deployedWithSolcVersion + }, EVM: ${networkConfig.deployedWithEvmVersion}" + echo "Constructor args: ${this.formatConstructorArgsForDisplay( + constructorArgs + )}" + `, + ]) } catch (error) { // If verification fails, continue with unverified deployment console.warn(`Contract verification failed: ${error}`) From a19368eef144e46feae5df6f847a515ce5672e94 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 9 Jul 2025 13:24:19 +0300 Subject: [PATCH 33/37] filter local anvil and use correct pkey for env --- script/deploy/deployToAllNetworks.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/script/deploy/deployToAllNetworks.sh b/script/deploy/deployToAllNetworks.sh index 3689dcb1b..f1520fd00 100755 --- a/script/deploy/deployToAllNetworks.sh +++ b/script/deploy/deployToAllNetworks.sh @@ -117,7 +117,7 @@ echo "Selected EVM version: $SELECTED_EVM_VERSION" # Handle network selection if [ -n "$NETWORKS_ARG" ]; then # Validate provided networks against available networks - AVAILABLE_NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) + AVAILABLE_NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false and .key != "localanvil") | .key' config/networks.json | sort) # Convert comma-separated list to array and validate each network IFS=',' read -ra NETWORK_ARRAY <<< "$NETWORKS_ARG" @@ -139,8 +139,8 @@ if [ -n "$NETWORKS_ARG" ]; then SELECTED_NETWORKS="$NETWORKS_ARG" echo "Using provided networks: $SELECTED_NETWORKS" else - # Extract network names from config/networks.json, filtering out zkEVM networks - NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false) | .key' config/networks.json | sort) + # Extract network names from config/networks.json, filtering out zkEVM networks and localanvil + NETWORKS=$(jq -r 'to_entries[] | select(.value.isZkEVM == false and .key != "localanvil") | .key' config/networks.json | sort) # Use gum to select networks (conditionally pre-select all) if [ "$SELECT_ALL_NETWORKS" = true ]; then @@ -178,8 +178,15 @@ fi echo "Update diamond: $UPDATE_DIAMOND" +# Determine which private key to use based on PRODUCTION environment variable +if [ "$PRODUCTION" = "true" ]; then + PRIVATE_KEY_ENV="env:PRIVATE_KEY_PRODUCTION" +else + PRIVATE_KEY_ENV="env:PRIVATE_KEY" +fi + # Build dagger command with selected options -DAGGER_CMD="deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS env:PRIVATE_KEY --salt=$SALT --evm-version=$SELECTED_EVM_VERSION --solc-version=$SELECTED_SOLC_VERSION" +DAGGER_CMD="deploy-to-all-networks . $SELECTED_CONTRACT $SELECTED_NETWORKS $PRIVATE_KEY_ENV --salt=$SALT --evm-version=$SELECTED_EVM_VERSION --solc-version=$SELECTED_SOLC_VERSION" # Add update-diamond flag if requested if [ "$UPDATE_DIAMOND" = true ]; then From 3a3487384a50fed8f5d8a76a4e367d69c4469a84 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 9 Jul 2025 13:56:34 +0300 Subject: [PATCH 34/37] ignore failed deployments, write new failed log on every run, improve apt caching --- .dagger/src/index.ts | 23 +++++++++++++++++++++-- .gitignore | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index a1bfbd66d..74e63968d 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -178,8 +178,17 @@ export class LifiContracts { ]) .withExec(['chown', '-R', 'foundry:foundry', '/workspace']) // Install Node.js, bun, and jq - .withExec(['apt-get', 'update']) - .withExec(['apt-get', 'install', '-y', 'nodejs', 'npm', 'jq']) + .withExec([ + 'apt-get', + 'update', + '&&', + 'apt-get', + 'install', + '-y', + 'nodejs', + 'npm', + 'jq', + ]) .withExec(['npm', 'install', '-g', 'bun']) .withUser('foundry') // Install dependencies @@ -839,6 +848,16 @@ export class LifiContracts { updateDiamond?: boolean, safeSignerPrivateKey?: Secret ): Promise { + // Remove failed_deployments.json at the start if it exists + try { + const failedLogFileName = 'failed_deployments_log.json' + await source.file(`deployments/${failedLogFileName}`).id() + // File exists, remove it by creating source without it + source = source.withoutFile(`deployments/${failedLogFileName}`) + } catch (error) { + // File doesn't exist, which is fine - no need to log this as an error + } + let updatedSource = source for (const network of networks) { diff --git a/.gitignore b/.gitignore index 23a279e61..7f9021f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,5 @@ test/solidity/TestPlayground.t.sol .aider* .claude* .direnv/ -.cursorrules \ No newline at end of file +.cursorrules +deployments/failed_deployments_log.json From 3fd720d8f5ff71e79e22449d1bec699ead042fba Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 9 Jul 2025 15:04:59 +0300 Subject: [PATCH 35/37] fix apt command --- .dagger/src/index.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 74e63968d..9b61e402a 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -179,15 +179,9 @@ export class LifiContracts { .withExec(['chown', '-R', 'foundry:foundry', '/workspace']) // Install Node.js, bun, and jq .withExec([ - 'apt-get', - 'update', - '&&', - 'apt-get', - 'install', - '-y', - 'nodejs', - 'npm', - 'jq', + 'bash', + '-c', + 'apt-get update && apt-get install -y nodejs npm jq', ]) .withExec(['npm', 'install', '-g', 'bun']) .withUser('foundry') From 42a2294691a25dd7bcf59c9a5e2ab75c5bf5ed05 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Wed, 9 Jul 2025 16:11:53 +0300 Subject: [PATCH 36/37] grab exact output of stderr and surface so it can be logged --- .dagger/src/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 9b61e402a..94c04af08 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -11,6 +11,7 @@ import { Secret, object, func, + ReturnType, } from '@dagger.io/dagger' interface NetworkConfig { @@ -270,7 +271,7 @@ export class LifiContracts { // Execute - return (await this.buildProject(source, solcVersion, evmVersion)) + const result = (await this.buildProject(source, solcVersion, evmVersion)) .withMountedDirectory( '/workspace/deployments', source.directory('deployments') @@ -284,7 +285,13 @@ export class LifiContracts { .withSecretVariable('PRIVATE_KEY', privateKey) .withEnvVariable('SALT', originalSalt) .withEnvVariable('DEPLOY_TO_DEFAULT_DIAMOND_ADDRESS', 'true') - .withExec(forgeArgs) + .withExec(forgeArgs, { expect: ReturnType.Any }) + + if ((await result.exitCode()) > 0) { + throw new Error(`Failed to deploy contract: ${await result.stderr()}`) + } + + return result } /** From 72eb513e5bc6bcc8fad2baf79908f112e87c3015 Mon Sep 17 00:00:00 2001 From: Ed Zynda Date: Thu, 10 Jul 2025 13:30:03 +0300 Subject: [PATCH 37/37] detect host arch --- .dagger/src/index.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.dagger/src/index.ts b/.dagger/src/index.ts index 94c04af08..c7c69f2e7 100644 --- a/.dagger/src/index.ts +++ b/.dagger/src/index.ts @@ -12,6 +12,7 @@ import { object, func, ReturnType, + Platform, } from '@dagger.io/dagger' interface NetworkConfig { @@ -27,6 +28,24 @@ interface NetworkConfig { export class LifiContracts { /// Utility Methods /// + private async detectHostArch(): Promise { + // Use uname to detect architecture + const output = await dag + .container() + .from('alpine:latest') + .withExec(['uname', '-m']) + .stdout() + + const arch = output.trim() + + // Common ARM64 identifiers + if (arch === 'aarch64' || arch === 'arm64') { + return 'linux/arm64' as Platform + } + + return 'linux/amd64' as Platform + } + /** * Parse JSON string with explicit error handling */ @@ -158,7 +177,9 @@ export class LifiContracts { return ( dag - .container() + .container({ + platform: await this.detectHostArch(), + }) .from('ghcr.io/foundry-rs/foundry:latest') .withDirectory('/workspace/src', source.directory('src')) .withDirectory('/workspace/lib', source.directory('lib'))