From 144063d614941faf0aa6f50c690791a6d1d46434 Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 3 Sep 2024 15:01:01 +0100 Subject: [PATCH] Deployment script: fix the addresses output The script now separates the protocol addresses from the each group of collateral addresses. This commit also updates the deployment-artifacts-to-app-env script to generate env vars separated in a similar way. --- contracts/utils/deploy-cli.ts | 116 +++++++++++++++++- .../utils/deployment-artifacts-to-app-env.ts | 114 +++++++++++------ 2 files changed, 188 insertions(+), 42 deletions(-) diff --git a/contracts/utils/deploy-cli.ts b/contracts/utils/deploy-cli.ts index 6b159c8d..78fa6125 100644 --- a/contracts/utils/deploy-cli.ts +++ b/contracts/utils/deploy-cli.ts @@ -40,6 +40,14 @@ e.g. --chain-id can be set via CHAIN_ID instead. Parameters take precedence over const ANVIL_FIRST_ACCOUNT = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; +const PROTOCOL_CONTRACTS_VALID_NAMES = [ + "WETHTester", + "BoldToken", + "CollateralRegistry", + "HintHelpers", + "MultiTroveGetter", +]; + const argv = minimist(process.argv.slice(2), { alias: { h: "help", @@ -185,27 +193,43 @@ Deploying Liquity contracts with the following settings: `broadcast/DeployLiquity2.s.sol/${options.chainId}/run-latest.json`, ); + const collateralContracts = await getAllCollateralsContracts(deployedContracts, options); + // XXX hotfix: we were leaking Github secrets in "deployer" // TODO: check if "deployer" is a private key, and calculate its address and use it instead? const { deployer, ...safeOptions } = options; + const protocolContracts = Object.fromEntries( + filterProtocolContracts(deployedContracts), + ); + // write env file await fs.writeJson("deployment-context-latest.json", { options: safeOptions, - deployedContracts: Object.fromEntries(deployedContracts), + deployedContracts, + collateralContracts, + protocolContracts, }); // format deployed contracts const longestContractName = Math.max( ...deployedContracts.map(([name]) => name.length), ); - const deployedContractsFormatted = deployedContracts - .map(([name, address]) => `${name.padEnd(longestContractName)} ${address}`) - .join("\n"); - echo("Contract deployment complete."); + const formatContracts = (contracts: Array) => + contracts.map(([name, address]) => ` ${name.padEnd(longestContractName)} ${address}`).join("\n"); + + echo("Protocol contracts:"); + echo(""); + echo(formatContracts(filterProtocolContracts(deployedContracts))); echo(""); - echo(deployedContractsFormatted); + echo( + collateralContracts.map((collateral, index) => ( + `Collateral ${index + 1} contracts:\n\n${formatContracts(Object.entries(collateral))}` + )).join("\n\n"), + ); + echo(""); + echo("Deployment complete."); echo(""); } @@ -255,6 +279,10 @@ async function getDeployedContracts(jsonPath: string) { throw new Error("Invalid deployment log: " + JSON.stringify(latestRun)); } +function filterProtocolContracts(contracts: Awaited>) { + return contracts.filter(([name]) => PROTOCOL_CONTRACTS_VALID_NAMES.includes(name)); +} + function safeParseInt(value: string) { const parsed = parseInt(value, 10); return isNaN(parsed) ? undefined : parsed; @@ -296,3 +324,79 @@ async function parseArgs() { return { options, networkPreset }; } + +async function castCall( + rpcUrl: string, + contract: string, + method: string, + ...args: string[] +) { + try { + const result = await $`cast call ${contract} ${method} ${args.join(" ")} --rpc-url '${rpcUrl}'`; + return result.stdout.trim(); + } catch (error) { + console.error(`Error calling ${contract} ${method} ${args.join(" ")}: ${error}`); + throw error; + } +} + +async function getCollateralContracts( + collateralIndex: number, + collateralRegistry: string, + rpcUrl: string, +) { + const [token, troveManager] = await Promise.all([ + castCall(rpcUrl, collateralRegistry, "getToken(uint256)(address)", String(collateralIndex)), + castCall(rpcUrl, collateralRegistry, "getTroveManager(uint256)(address)", String(collateralIndex)), + ]); + + const [ + activePool, + borrowerOperations, + sortedTroves, + stabilityPool, + ] = await Promise.all([ + castCall(rpcUrl, troveManager, "activePool()(address)"), + castCall(rpcUrl, troveManager, "borrowerOperations()(address)"), + castCall(rpcUrl, troveManager, "sortedTroves()(address)"), + castCall(rpcUrl, troveManager, "stabilityPool()(address)"), + ]); + + return { + activePool, + borrowerOperations, + sortedTroves, + stabilityPool, + token, + troveManager, + }; +} + +async function getAllCollateralsContracts( + deployedContracts: Array, + options: Awaited>["options"], +) { + const deployedContractsRecord = Object.fromEntries(deployedContracts); + + const ccall = async (contract: string, method: string, ...args: string[]) => { + const result = await $`cast call ${contract} ${method} ${args.join(" ")} --rpc-url '${options.rpcUrl}'`; + return result.stdout.trim(); + }; + + const totalCollaterals = Number( + await ccall( + deployedContractsRecord.CollateralRegistry, + "totalCollaterals()", + ), + ); + + return Promise.all( + Array.from({ length: totalCollaterals }, (_, index) => ( + getCollateralContracts( + index, + deployedContractsRecord.CollateralRegistry, + options.rpcUrl, + ) + )), + ); +} diff --git a/contracts/utils/deployment-artifacts-to-app-env.ts b/contracts/utils/deployment-artifacts-to-app-env.ts index 211040e7..e11cf6d4 100644 --- a/contracts/utils/deployment-artifacts-to-app-env.ts +++ b/contracts/utils/deployment-artifacts-to-app-env.ts @@ -33,14 +33,31 @@ const argv = minimist(process.argv.slice(2), { const ZAddress = z.string().regex(/^0x[0-9a-fA-F]{40}$/); const ZDeploymentContext = z.object({ - deployedContracts: z.record(ZAddress), + deployedContracts: z.array(z.tuple([z.string(), ZAddress])), + collateralContracts: z.array( + z.object({ + activePool: ZAddress, + borrowerOperations: ZAddress, + sortedTroves: ZAddress, + stabilityPool: ZAddress, + token: ZAddress, + troveManager: ZAddress, + }), + ), + protocolContracts: z.object({ + BoldToken: ZAddress, + CollateralRegistry: ZAddress, + HintHelpers: ZAddress, + MultiTroveGetter: ZAddress, + WETHTester: ZAddress, + }), }); type DeploymentContext = z.infer; const NULL_ADDRESS = `0x${"0".repeat(40)}`; -export async function main() { +export function main() { const options = { help: argv["help"], append: argv["append"], @@ -58,12 +75,12 @@ export async function main() { process.exit(1); } - const { deployedContracts } = parseDeploymentContext( - await fs.readFile(options.inputJsonPath, "utf-8"), + const deploymentContext = parseDeploymentContext( + fs.readFileSync(options.inputJsonPath, "utf-8"), ); const outputEnv = objectToEnvironmentVariables( - deployedContractsToAppEnvVariables(deployedContracts), + deployedContractsToAppEnvVariables(deploymentContext), ); if (!options.outputEnvPath) { @@ -71,11 +88,11 @@ export async function main() { process.exit(0); } - await fs.ensureFile(options.outputEnvPath); + fs.ensureFileSync(options.outputEnvPath); if (options.append) { - await fs.appendFile(options.outputEnvPath, `\n${outputEnv}\n`); + fs.appendFileSync(options.outputEnvPath, `\n${outputEnv}\n`); } else { - await fs.writeFile(options.outputEnvPath, `${outputEnv}\n`); + fs.writeFileSync(options.outputEnvPath, `${outputEnv}\n`); } console.log(`\nEnvironment variables written to ${options.outputEnvPath}.\n`); @@ -85,14 +102,24 @@ function objectToEnvironmentVariables(object: Record) { return Object.entries(object) .map(([key, value]) => `${key}=${value}`) .sort() + .sort((a, b) => { + if (a.includes("_COLL_") && !b.includes("_COLL_")) { + return -1; + } + if (!a.includes("_COLL_") && b.includes("_COLL_")) { + return 1; + } + return 0; + }) .join("\n"); } -function deployedContractsToAppEnvVariables(deployedContracts: DeploymentContext["deployedContracts"]) { +function deployedContractsToAppEnvVariables(deployedContext: DeploymentContext) { const appEnvVariables: Record = {}; - for (const [contractName, address] of Object.entries(deployedContracts)) { - const envVarName = contractNameToAppEnvVariable(contractName); + // protocol contracts + for (const [contractName, address] of Object.entries(deployedContext.protocolContracts)) { + const envVarName = contractNameToAppEnvVariable(contractName, "CONTRACT"); if (envVarName) { appEnvVariables[envVarName] = address; } @@ -101,36 +128,48 @@ function deployedContractsToAppEnvVariables(deployedContracts: DeploymentContext appEnvVariables.NEXT_PUBLIC_CONTRACT_FUNCTION_CALLER = NULL_ADDRESS; appEnvVariables.NEXT_PUBLIC_CONTRACT_HINT_HELPERS = NULL_ADDRESS; + // collateral contracts + for (const [index, contract] of Object.entries(deployedContext.collateralContracts)) { + for (const [contractName, address] of Object.entries(contract)) { + const envVarName = contractNameToAppEnvVariable(contractName, `COLL_${index}_CONTRACT`); + if (envVarName) { + appEnvVariables[envVarName] = address; + } + } + } + return appEnvVariables; } -function contractNameToAppEnvVariable(contractName: string) { +function contractNameToAppEnvVariable(contractName: string, prefix: string = "") { + prefix = `NEXT_PUBLIC_${prefix}`; switch (contractName) { - case "ActivePool": - return "NEXT_PUBLIC_CONTRACT_ACTIVE_POOL"; + // protocol contracts case "BoldToken": - return "NEXT_PUBLIC_CONTRACT_BOLD_TOKEN"; - case "BorrowerOperations": - return "NEXT_PUBLIC_CONTRACT_BORROWER_OPERATIONS"; - case "CollSurplusPool": - return "NEXT_PUBLIC_CONTRACT_COLL_SURPLUS_POOL"; - case "DefaultPool": - return "NEXT_PUBLIC_CONTRACT_DEFAULT_POOL"; - case "ERC20Faucet": - return "NEXT_PUBLIC_CONTRACT_COLL_TOKEN"; - case "GasPool": - return "NEXT_PUBLIC_CONTRACT_GAS_POOL"; - case "MockInterestRouter": - return "NEXT_PUBLIC_CONTRACT_INTEREST_ROUTER"; - case "PriceFeedTestnet": - return "NEXT_PUBLIC_CONTRACT_PRICE_FEED"; - case "SortedTroves": - return "NEXT_PUBLIC_CONTRACT_SORTED_TROVES"; - case "StabilityPool": - return "NEXT_PUBLIC_CONTRACT_STABILITY_POOL"; - case "TroveManager": - case "TroveManagerTester": - return "NEXT_PUBLIC_CONTRACT_TROVE_MANAGER"; + return `${prefix}_BOLD_TOKEN`; + case "CollateralRegistry": + return `${prefix}_COLLATERAL_REGISTRY`; + case "HintHelpers": + return `${prefix}_HINT_HELPERS`; + case "MultiTroveGetter": + return `${prefix}_MULTI_TROVE_GETTER`; + case "WETH": + case "WETHTester": + return `${prefix}_WETH`; + + // collateral contracts + case "activePool": + return `${prefix}_ACTIVE_POOL`; + case "borrowerOperations": + return `${prefix}_BORROWER_OPERATIONS`; + case "sortedTroves": + return `${prefix}_SORTED_TROVES`; + case "stabilityPool": + return `${prefix}_STABILITY_POOL`; + case "token": + return `${prefix}_TOKEN`; + case "troveManager": + return `${prefix}_TROVE_MANAGER`; } return null; } @@ -158,6 +197,9 @@ function parseDeploymentContext(content: string) { }).join("\n"), ); console.error(""); + console.error("Received:"); + console.error(JSON.stringify(json, null, 2)); + process.exit(1); }