deploy_evm_lazer_contracts.ts 11 KB

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