Skip to content

Commit be61480

Browse files
committed
chore(lazer) Lazer Upgrade Script
1 parent 584112d commit be61480

File tree

3 files changed

+92
-87
lines changed

3 files changed

+92
-87
lines changed

contract_manager/scripts/upgrade_evm_lazer_contracts.ts

Lines changed: 50 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/* eslint-disable @typescript-eslint/restrict-template-expressions */
22
/* eslint-disable no-console */
33
import { execSync } from "node:child_process";
4+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
45
import path from "node:path";
6+
import { fileURLToPath } from "node:url";
57

68
import type { PythCluster } from "@pythnetwork/client/lib/cluster";
79
import yargs from "yargs";
@@ -39,98 +41,76 @@ function registry(cluster: PythCluster): string {
3941
return RPCS[cluster];
4042
}
4143

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;
4847
};
4948

5049
function deployLazerImplementation(
5150
chain: string,
5251
rpcUrl: string,
5352
privateKey: string,
53+
chainNetworkId: number,
5454
): 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()"`;
5968

6069
try {
6170
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...");
6280
const output = execSync(forgeCommand, {
6381
cwd: lazerContractsDir,
6482
encoding: "utf8",
6583
stdio: "pipe",
6684
});
6785

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);
8787

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+
);
11193
}
11294

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;
12198

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+
);
129104
}
130105

131-
throw new Error(
132-
"Could not extract implementation address from deployment output",
106+
console.log(
107+
`\nPythLazer implementation deployed at: ${upgradeOutput.implementationAddress}`,
133108
);
109+
110+
// Clean up the output file
111+
unlinkSync(upgradeOutputPath);
112+
113+
return Promise.resolve(upgradeOutput.implementationAddress);
134114
} catch (error) {
135115
console.error("Deployment failed:", error);
136116
throw error;
@@ -167,6 +147,7 @@ async function main() {
167147
contract.chain.getId(),
168148
contract.chain.rpcUrl,
169149
argv["private-key"],
150+
contract.chain.networkId,
170151
);
171152
},
172153
);

lazer/contracts/evm/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ docs/
1515
.env
1616

1717
dist/
18+
19+
# Lazer contract deployment output files
20+
upgrade-implementation-output.json
21+
deployment-output.json

lazer/contracts/evm/script/PythLazerDeploy.s.sol

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -180,23 +180,7 @@ contract PythLazerDeployScript is Script {
180180
address proxy = deployProxy("lazer:proxy", impl);
181181

182182
// Write deployment output to JSON file for programmatic access
183-
_writeDeploymentOutput(impl, proxy);
184-
}
185-
186-
function _writeDeploymentOutput(address impl, address proxy) internal {
187-
string memory jsonKey = "deployment";
188-
189-
vm.serializeAddress(jsonKey, "implementationAddress", impl);
190-
vm.serializeUint(jsonKey, "chainId", block.chainid);
191-
string memory finalJson = vm.serializeAddress(
192-
jsonKey,
193-
"proxyAddress",
194-
proxy
195-
);
196-
197-
vm.writeJson(finalJson, "./deployment-output.json");
198-
199-
console.log("Deployment output written to deployment-output.json");
183+
_writeOutput(impl, proxy, "./deployment-output.json");
200184
}
201185

202186
// Commented this out as we upgrade the contract using the upgrade_evm_lazer_contracts.ts script
@@ -215,6 +199,42 @@ contract PythLazerDeployScript is Script {
215199
// This function can be called via: forge script --sig "deployImplementationForUpgrade()"
216200
function deployImplementationForUpgrade() public {
217201
address impl = deployImplementation("lazer:impl");
218-
console.log("Implementation address for upgrade: %s", impl);
202+
_writeOutput(impl, address(0), "./upgrade-implementation-output.json");
203+
}
204+
205+
/**
206+
* @dev Writes deployment/upgrade output to a JSON file
207+
* @param impl The implementation contract address
208+
* @param proxy The proxy contract address (use address(0) if not applicable)
209+
* @param outputPath The path to write the JSON file to
210+
*/
211+
function _writeOutput(
212+
address impl,
213+
address proxy,
214+
string memory outputPath
215+
) internal {
216+
string memory jsonKey = "output";
217+
218+
vm.serializeAddress(jsonKey, "implementationAddress", impl);
219+
220+
string memory finalJson;
221+
if (proxy != address(0)) {
222+
// Include proxy address for full deployment
223+
// Serialize chainId, then proxyAddress (last call returns final JSON)
224+
vm.serializeUint(jsonKey, "chainId", block.chainid);
225+
finalJson = vm.serializeAddress(jsonKey, "proxyAddress", proxy);
226+
} else {
227+
// Only implementation and chainId for upgrade-only deployments
228+
// chainId serialization is the last call, so it returns the final JSON
229+
finalJson = vm.serializeUint(jsonKey, "chainId", block.chainid);
230+
}
231+
232+
vm.writeJson(finalJson, outputPath);
233+
234+
console.log("Implementation address: %s", impl);
235+
if (proxy != address(0)) {
236+
console.log("Proxy address: %s", proxy);
237+
}
238+
console.log("Output written to %s", outputPath);
219239
}
220240
}

0 commit comments

Comments
 (0)