deploy_evm_lazer_contracts.ts 12 KB

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