Skip to content

Commit fc7b8b0

Browse files
committed
chore(contract-manager) Lazer Deploy scripts
1 parent 5a2f9c6 commit fc7b8b0

File tree

5 files changed

+505
-2
lines changed

5 files changed

+505
-2
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
/**
2+
* PythLazer EVM Contract Deployment and Management Script
3+
*
4+
* This script provides functionality to deploy PythLazer contracts and manage trusted signers
5+
* on EVM-compatible blockchains. It integrates with the DefaultStore system and supports
6+
* both deployment and contract management operations.
7+
*
8+
* FLAGS AND USAGE:
9+
*
10+
* 1. DEPLOYMENT FLAGS:
11+
* --deploy Deploy the PythLazer contract (default: true)
12+
* --verify Verify contract on block explorer after deployment
13+
* --etherscan-api-key <key> Required if --verify is true
14+
*
15+
* 2. TRUSTED SIGNER MANAGEMENT:
16+
* --update-signer <address> Address of the trusted signer to add/update
17+
* --expires-at <timestamp> Unix timestamp when the signer expires
18+
*
19+
* EXAMPLES:
20+
*
21+
* Deploy only:
22+
* node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key>
23+
*
24+
* Deploy with verification:
25+
* node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key> --verify --etherscan-api-key <key>
26+
*
27+
* Update trusted signer only (requires existing contract):
28+
* node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key> --deploy false --update-signer 0x123... --expires-at 1735689600
29+
*
30+
* Deploy and update trusted signer in one command:
31+
* node deploy_evm_lazer_contracts.ts --chain ethereum --private-key <key> --update-signer 0x123... --expires-at 1735689600
32+
*
33+
* NOTES:
34+
* - The --deploy flag defaults to true if no other flags are specified
35+
* - Both --update-signer and --expires-at must be provided together
36+
* - If updating trusted signer without deploying, an existing contract must be found
37+
* - The script automatically saves deployed contracts to the store and updates EvmLazerContracts.json
38+
* - All operations use the chain's RPC URL from the DefaultStore
39+
*/
40+
41+
import yargs from "yargs";
42+
import { hideBin } from "yargs/helpers";
43+
import { execSync } from "child_process";
44+
import { readFileSync, writeFileSync } from "fs";
45+
import { join } from "path";
46+
import { DefaultStore } from "../src/node/utils/store";
47+
import { EvmChain } from "../src/core/chains";
48+
import { EvmLazerContract } from "../src/core/contracts/evm";
49+
import { toPrivateKey, PrivateKey } from "../src/core/base";
50+
51+
const parser = yargs(hideBin(process.argv))
52+
.usage(
53+
"Deploys PythLazer contracts and/or updates trusted signers\n" +
54+
"Usage: $0 --chain <chain_name> --private-key <private_key> [--deploy] [--update-signer <address> --expires-at <timestamp>]",
55+
)
56+
.options({
57+
chain: {
58+
type: "string",
59+
description: "Chain name to deploy to (from EvmChains.json)",
60+
demandOption: true,
61+
},
62+
"private-key": {
63+
type: "string",
64+
description: "Private key for deployment and transactions",
65+
demandOption: true,
66+
},
67+
deploy: {
68+
type: "boolean",
69+
description:
70+
"Deploy the PythLazer contract (default: true if no other flags specified)",
71+
default: true,
72+
},
73+
verify: {
74+
type: "boolean",
75+
description:
76+
"Verify contract on block explorer (only used with --deploy)",
77+
default: false,
78+
},
79+
"etherscan-api-key": {
80+
type: "string",
81+
description:
82+
"Etherscan API key for verification (required if --verify is true)",
83+
},
84+
"update-signer": {
85+
type: "string",
86+
description: "Update trusted signer address (requires --expires-at)",
87+
},
88+
"expires-at": {
89+
type: "number",
90+
description:
91+
"Expiration timestamp for trusted signer in Unix timestamp format (required if --update-signer is specified)",
92+
},
93+
})
94+
.check((argv) => {
95+
// If update-signer is specified, expires-at must also be specified
96+
if (argv["update-signer"] && !argv["expires-at"]) {
97+
throw new Error(
98+
"--expires-at is required when --update-signer is specified",
99+
);
100+
}
101+
102+
// If expires-at is specified, update-signer must also be specified
103+
if (argv["expires-at"] && !argv["update-signer"]) {
104+
throw new Error(
105+
"--update-signer is required when --expires-at is specified",
106+
);
107+
}
108+
109+
// If verify is true, etherscan-api-key must be provided
110+
if (argv.verify && !argv["etherscan-api-key"]) {
111+
throw new Error("--etherscan-api-key is required when --verify is true");
112+
}
113+
114+
return true;
115+
});
116+
117+
/**
118+
* Deploys the PythLazer contract using forge script
119+
* @param chain The EVM chain to deploy to
120+
* @param privateKey The private key for deployment
121+
* @param verify Whether to verify the contract
122+
* @param etherscanApiKey The Etherscan API key for verification
123+
* @returns The deployed contract address
124+
*/
125+
async function deployPythLazerContract(
126+
chain: EvmChain,
127+
privateKey: string,
128+
verify: boolean,
129+
etherscanApiKey?: string,
130+
): Promise<string> {
131+
const lazerContractsDir = join(__dirname, "../../lazer/contracts/evm");
132+
const rpcUrl = chain.rpcUrl;
133+
134+
console.log(`Deploying PythLazer contract to ${chain.getId()}...`);
135+
console.log(`RPC URL: ${rpcUrl}`);
136+
137+
// Build forge command
138+
let forgeCommand = `forge script script/PythLazerDeploy.s.sol --rpc-url ${rpcUrl} --private-key ${privateKey} --broadcast`;
139+
140+
if (verify && etherscanApiKey) {
141+
forgeCommand += ` --verify --etherscan-api-key ${etherscanApiKey}`;
142+
}
143+
144+
try {
145+
// Execute forge script
146+
console.log("Running forge deployment script...");
147+
const output = execSync(forgeCommand, {
148+
cwd: lazerContractsDir,
149+
encoding: "utf8",
150+
stdio: "pipe",
151+
});
152+
153+
console.log("Deployment output:");
154+
console.log(output);
155+
156+
// Extract proxy address from output
157+
const proxyMatch = output.match(/Deployed proxy to: (0x[a-fA-F0-9]{40})/);
158+
if (!proxyMatch) {
159+
throw new Error("Could not extract proxy address from deployment output");
160+
}
161+
162+
const proxyAddress = proxyMatch[1];
163+
console.log(`\nPythLazer proxy deployed at: ${proxyAddress}`);
164+
165+
return proxyAddress;
166+
} catch (error) {
167+
console.error("Deployment failed:", error);
168+
throw error;
169+
}
170+
}
171+
172+
/**
173+
* Updates the EvmLazerContracts.json file with the new deployment
174+
* @param chain The chain where the contract was deployed
175+
* @param address The deployed contract address
176+
*/
177+
function updateContractsFile(chain: EvmChain, address: string): void {
178+
const contractsJsonPath = join(
179+
__dirname,
180+
"../store/contracts/EvmLazerContracts.json",
181+
);
182+
183+
DefaultStore.lazer_contracts[`${chain.getId()}_${address}`] =
184+
new EvmLazerContract(chain, address);
185+
DefaultStore.saveAllContracts();
186+
187+
console.log(`\nUpdated EvmLazerContracts.json with new deployment`);
188+
console.log(`Chain: ${chain.getId()}`);
189+
console.log(`Address: ${address}`);
190+
}
191+
192+
/**
193+
* Gets or creates an EvmLazerContract instance
194+
* @param chain The EVM chain
195+
* @param address The contract address
196+
* @returns The EvmLazerContract instance
197+
*/
198+
function getOrCreateLazerContract(
199+
chain: EvmChain,
200+
address: string,
201+
): EvmLazerContract {
202+
return new EvmLazerContract(chain, address);
203+
}
204+
205+
/**
206+
* Updates the trusted signer for a PythLazer contract
207+
* @param chain The EVM chain
208+
* @param contractAddress The contract address
209+
* @param trustedSigner The trusted signer address
210+
* @param expiresAt The expiration timestamp
211+
* @param privateKey The private key for the transaction
212+
*/
213+
async function updateTrustedSigner(
214+
chain: EvmChain,
215+
contractAddress: string,
216+
trustedSigner: string,
217+
expiresAt: number,
218+
privateKey: PrivateKey,
219+
): Promise<void> {
220+
console.log(`Updating trusted signer on ${chain.getId()}...`);
221+
console.log(`Contract: ${contractAddress}`);
222+
console.log(`Trusted Signer: ${trustedSigner}`);
223+
console.log(`Expires At: ${new Date(expiresAt * 1000).toISOString()}`);
224+
225+
const contract = getOrCreateLazerContract(chain, contractAddress);
226+
await contract.updateTrustedSigner(trustedSigner, expiresAt, privateKey);
227+
}
228+
229+
function findLazerContract(chain: EvmChain): EvmLazerContract | undefined {
230+
for (const contract of Object.values(DefaultStore.lazer_contracts)) {
231+
if (
232+
contract instanceof EvmLazerContract &&
233+
contract.chain.getId() === chain.getId()
234+
) {
235+
console.log(
236+
`Found lazer contract for ${chain.getId()} at ${contract.address}`,
237+
);
238+
return contract;
239+
}
240+
}
241+
}
242+
243+
export async function findOrDeployPythLazerContract(
244+
chain: EvmChain,
245+
privateKey: string,
246+
verify: boolean,
247+
etherscanApiKey?: string,
248+
): Promise<string> {
249+
const lazerContract = findLazerContract(chain);
250+
if (lazerContract) {
251+
console.log(
252+
`Found lazer contract for ${chain.getId()} at ${lazerContract.address}`,
253+
);
254+
return lazerContract.address;
255+
}
256+
const deployedAddress = await deployPythLazerContract(
257+
chain,
258+
privateKey,
259+
verify,
260+
etherscanApiKey,
261+
);
262+
console.log(
263+
`✅ PythLazer contract deployed successfully at ${deployedAddress}`,
264+
);
265+
updateContractsFile(chain, deployedAddress);
266+
return deployedAddress;
267+
}
268+
269+
export async function main() {
270+
const argv = await parser.argv;
271+
272+
// Get the chain from the store
273+
const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain);
274+
275+
try {
276+
let deployedAddress: string | undefined;
277+
278+
// Step 1: Deploy contract if requested
279+
if (argv.deploy) {
280+
console.log(`Deploying PythLazer contract to ${chain.getId()}...`);
281+
console.log(`Chain: ${chain.getId()}`);
282+
console.log(`RPC URL: ${chain.rpcUrl}`);
283+
console.log(`Verification: ${argv.verify ? "Enabled" : "Disabled"}`);
284+
285+
deployedAddress = await findOrDeployPythLazerContract(
286+
chain,
287+
argv["private-key"],
288+
argv.verify,
289+
argv["etherscan-api-key"],
290+
);
291+
}
292+
293+
// Step 2: Update trusted signer if requested
294+
if (argv["update-signer"] && argv["expires-at"]) {
295+
console.log(`Updating trusted signer on ${chain.getId()}...`);
296+
console.log(`Signer Address: ${argv["update-signer"]}`);
297+
console.log(
298+
`Expires At: ${new Date(argv["expires-at"] * 1000).toISOString()}`,
299+
);
300+
301+
let contractAddress: string;
302+
303+
// Use deployed address if we just deployed, otherwise find existing contract
304+
if (deployedAddress) {
305+
contractAddress = deployedAddress;
306+
console.log(`Using newly deployed contract at ${contractAddress}`);
307+
} else {
308+
const lazerContract = findLazerContract(chain);
309+
if (lazerContract) {
310+
contractAddress = lazerContract.address;
311+
console.log(`Using existing contract at ${contractAddress}`);
312+
} else {
313+
throw new Error(
314+
`No lazer contract found for ${chain.getId()}. Deploy a contract first using --deploy.`,
315+
);
316+
}
317+
}
318+
319+
await updateTrustedSigner(
320+
chain,
321+
contractAddress,
322+
argv["update-signer"],
323+
argv["expires-at"],
324+
toPrivateKey(argv["private-key"]),
325+
);
326+
327+
console.log(`✅ Trusted signer updated successfully`);
328+
}
329+
330+
// Summary
331+
console.log(`\n Operation Summary:`);
332+
if (argv.deploy && argv["update-signer"]) {
333+
console.log(`✅ Contract deployed at: ${deployedAddress}`);
334+
console.log(`Trusted signer updated: ${argv["update-signer"]}`);
335+
console.log(
336+
`Expires at: ${new Date(argv["expires-at"]! * 1000).toISOString()}`,
337+
);
338+
} else if (argv.deploy) {
339+
console.log(`Contract deployed at ${deployedAddress}`);
340+
} else if (argv["update-signer"]) {
341+
console.log(`Trusted signer updated successfully`);
342+
} else {
343+
console.log(
344+
`No operations performed. Use --deploy to deploy or --update-signer to update trusted signer.`,
345+
);
346+
}
347+
} catch (error) {
348+
console.error("Operation failed:", error);
349+
process.exit(1);
350+
}
351+
}
352+
353+
main();

0 commit comments

Comments
 (0)