Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deployment script: fix the addresses output #405

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 110 additions & 6 deletions contracts/utils/deploy-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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<string[]>) =>
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("");
}

Expand Down Expand Up @@ -255,6 +279,10 @@ async function getDeployedContracts(jsonPath: string) {
throw new Error("Invalid deployment log: " + JSON.stringify(latestRun));
}

function filterProtocolContracts(contracts: Awaited<ReturnType<typeof getDeployedContracts>>) {
return contracts.filter(([name]) => PROTOCOL_CONTRACTS_VALID_NAMES.includes(name));
}

function safeParseInt(value: string) {
const parsed = parseInt(value, 10);
return isNaN(parsed) ? undefined : parsed;
Expand Down Expand Up @@ -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<string[]>,
options: Awaited<ReturnType<typeof parseArgs>>["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,
)
)),
);
}
114 changes: 78 additions & 36 deletions contracts/utils/deployment-artifacts-to-app-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof ZDeploymentContext>;

const NULL_ADDRESS = `0x${"0".repeat(40)}`;

export async function main() {
export function main() {
const options = {
help: argv["help"],
append: argv["append"],
Expand All @@ -58,24 +75,24 @@ 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) {
console.log(outputEnv);
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`);
Expand All @@ -85,14 +102,24 @@ function objectToEnvironmentVariables(object: Record<string, unknown>) {
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<string, string> = {};

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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}

Expand Down
Loading