|
1 | 1 | /* eslint-disable @typescript-eslint/restrict-template-expressions */ |
2 | 2 | /* eslint-disable no-console */ |
3 | 3 | import { execSync } from "node:child_process"; |
| 4 | +import { existsSync, readFileSync, unlinkSync } from "node:fs"; |
4 | 5 | import path from "node:path"; |
| 6 | +import { fileURLToPath } from "node:url"; |
5 | 7 |
|
6 | 8 | import type { PythCluster } from "@pythnetwork/client/lib/cluster"; |
7 | 9 | import yargs from "yargs"; |
@@ -39,98 +41,76 @@ function registry(cluster: PythCluster): string { |
39 | 41 | return RPCS[cluster]; |
40 | 42 | } |
41 | 43 |
|
42 | | -type ForgeScriptOutput = { |
43 | | - logs?: { |
44 | | - level?: string; |
45 | | - msg?: string; |
46 | | - }[]; |
47 | | - returns?: Record<string, unknown>; |
| 44 | +type UpgradeImplementationOutput = { |
| 45 | + implementationAddress: string; |
| 46 | + chainId: number; |
48 | 47 | }; |
49 | 48 |
|
50 | 49 | function deployLazerImplementation( |
51 | 50 | chain: string, |
52 | 51 | rpcUrl: string, |
53 | 52 | privateKey: string, |
| 53 | + chainNetworkId: number, |
54 | 54 | ): Promise<string> { |
55 | | - const lazerContractsDir = path.resolve("../../lazer/contracts/evm"); |
56 | | - |
57 | | - // Build forge command to deploy only the implementation with JSON output |
58 | | - const forgeCommand = `forge script script/PythLazerDeploy.s.sol --rpc-url ${rpcUrl} --private-key ${privateKey} --broadcast --sig "deployImplementationForUpgrade()" --json`; |
| 55 | + // Resolve path relative to this script's location, not CWD |
| 56 | + const scriptDir = path.dirname(fileURLToPath(import.meta.url)); |
| 57 | + const lazerContractsDir = path.resolve( |
| 58 | + scriptDir, |
| 59 | + "../../lazer/contracts/evm", |
| 60 | + ); |
| 61 | + const upgradeOutputPath = path.join( |
| 62 | + lazerContractsDir, |
| 63 | + "upgrade-implementation-output.json", |
| 64 | + ); |
| 65 | + |
| 66 | + // Build forge command to deploy only the implementation |
| 67 | + const forgeCommand = `forge script script/PythLazerDeploy.s.sol --rpc-url ${rpcUrl} --private-key ${privateKey} --broadcast --sig "deployImplementationForUpgrade()"`; |
59 | 68 |
|
60 | 69 | try { |
61 | 70 | console.log(`Deploying PythLazer implementation to ${chain}...`); |
| 71 | + console.log(`RPC URL: ${rpcUrl}`); |
| 72 | + |
| 73 | + // Clean up any previous upgrade output |
| 74 | + if (existsSync(upgradeOutputPath)) { |
| 75 | + unlinkSync(upgradeOutputPath); |
| 76 | + } |
| 77 | + |
| 78 | + // Execute forge script |
| 79 | + console.log("Running forge deployment script..."); |
62 | 80 | const output = execSync(forgeCommand, { |
63 | 81 | cwd: lazerContractsDir, |
64 | 82 | encoding: "utf8", |
65 | 83 | stdio: "pipe", |
66 | 84 | }); |
67 | 85 |
|
68 | | - // Parse JSON output |
69 | | - let jsonOutput: ForgeScriptOutput; |
70 | | - try { |
71 | | - // Forge may output multiple JSON objects (one per line) or a single JSON object |
72 | | - // Try to parse as single JSON first, then as newline-delimited JSON |
73 | | - const lines = output.trim().split("\n"); |
74 | | - const lastLine = lines.at(-1); |
75 | | - if (!lastLine) { |
76 | | - throw new Error("Empty output from forge script"); |
77 | | - } |
78 | | - jsonOutput = JSON.parse(lastLine) as ForgeScriptOutput; |
79 | | - } catch (parseError) { |
80 | | - // If JSON parsing fails, fall back to regex parsing for error messages |
81 | | - console.log("Deployment output (non-JSON):"); |
82 | | - console.log(output); |
83 | | - const errorMessage = |
84 | | - parseError instanceof Error ? parseError.message : String(parseError); |
85 | | - throw new Error(`Failed to parse forge script JSON output: ${errorMessage}`); |
86 | | - } |
| 86 | + console.log(output); |
87 | 87 |
|
88 | | - // Extract implementation address from console logs in JSON output |
89 | | - // The deployImplementation function logs multiple possible formats: |
90 | | - // - "Deployed implementation to: <address>" |
91 | | - // - "Implementation already deployed at: <address>" |
92 | | - // - "Implementation address for upgrade: <address>" (from deployImplementationForUpgrade) |
93 | | - const addressPattern = /(0x[a-fA-F0-9]{40})/; |
94 | | - |
95 | | - if (jsonOutput.logs) { |
96 | | - for (const log of jsonOutput.logs) { |
97 | | - const msg = log.msg ?? ""; |
98 | | - if ( |
99 | | - msg.includes("Deployed implementation to:") || |
100 | | - msg.includes("Implementation already deployed at:") || |
101 | | - msg.includes("Implementation address for upgrade:") |
102 | | - ) { |
103 | | - const match = addressPattern.exec(msg); |
104 | | - const implAddress = match?.[1]; |
105 | | - if (implAddress) { |
106 | | - console.log(`\nPythLazer implementation address: ${implAddress}`); |
107 | | - return Promise.resolve(implAddress); |
108 | | - } |
109 | | - } |
110 | | - } |
| 88 | + // Read upgrade output from JSON file written by the forge script |
| 89 | + if (!existsSync(upgradeOutputPath)) { |
| 90 | + throw new Error( |
| 91 | + "Upgrade output file not found. Deployment may have failed.", |
| 92 | + ); |
111 | 93 | } |
112 | 94 |
|
113 | | - // Fallback: try to extract from raw output if logs structure is different |
114 | | - console.log("Deployment output:"); |
115 | | - console.log(output); |
116 | | - const patterns = [ |
117 | | - /Deployed implementation to: (0x[a-fA-F0-9]{40})/, |
118 | | - /Implementation already deployed at: (0x[a-fA-F0-9]{40})/, |
119 | | - /Implementation address for upgrade: (0x[a-fA-F0-9]{40})/, |
120 | | - ]; |
| 95 | + const upgradeOutput = JSON.parse( |
| 96 | + readFileSync(upgradeOutputPath, "utf8"), |
| 97 | + ) as UpgradeImplementationOutput; |
121 | 98 |
|
122 | | - for (const pattern of patterns) { |
123 | | - const match = pattern.exec(output); |
124 | | - const implAddress = match?.[1]; |
125 | | - if (implAddress) { |
126 | | - console.log(`\nPythLazer implementation address: ${implAddress}`); |
127 | | - return Promise.resolve(implAddress); |
128 | | - } |
| 99 | + // Verify chain ID matches |
| 100 | + if (upgradeOutput.chainId !== chainNetworkId) { |
| 101 | + throw new Error( |
| 102 | + `Chain ID mismatch: expected ${chainNetworkId}, got ${upgradeOutput.chainId}`, |
| 103 | + ); |
129 | 104 | } |
130 | 105 |
|
131 | | - throw new Error( |
132 | | - "Could not extract implementation address from deployment output", |
| 106 | + console.log( |
| 107 | + `\nPythLazer implementation deployed at: ${upgradeOutput.implementationAddress}`, |
133 | 108 | ); |
| 109 | + |
| 110 | + // Clean up the output file |
| 111 | + unlinkSync(upgradeOutputPath); |
| 112 | + |
| 113 | + return Promise.resolve(upgradeOutput.implementationAddress); |
134 | 114 | } catch (error) { |
135 | 115 | console.error("Deployment failed:", error); |
136 | 116 | throw error; |
@@ -167,6 +147,7 @@ async function main() { |
167 | 147 | contract.chain.getId(), |
168 | 148 | contract.chain.rpcUrl, |
169 | 149 | argv["private-key"], |
| 150 | + contract.chain.networkId, |
170 | 151 | ); |
171 | 152 | }, |
172 | 153 | ); |
|
0 commit comments