Skip to content
Open
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
31 changes: 31 additions & 0 deletions src/commands/deploy/cmd/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,37 @@ export const cleanContractName = (contractName: string) => {

return contractName;
}

/**
* Resolves the artifact name for a contract, trying with prefix first if provided.
*
* @param contractName - The contract name from the deployment event (e.g., "Directory_Impl")
* @param prefix - Optional prefix to try prepending (e.g., "EigenDA")
* @param zeusConfigDirName - The Zeus config directory path
* @param pathResolver - Function to resolve the full path (e.g., canonicalPaths.contractInformation)
* @returns The final contract name to use for artifact resolution
*/
export const resolveArtifactName = (
contractName: string,
prefix: string | undefined,
zeusConfigDirName: string,
pathResolver: (dir: string, name: string) => string
): string => {
const cleanedContractName = cleanContractName(contractName);

if (prefix) {
const prefixedName = `${prefix}${cleanedContractName}`;
const prefixedPath = pathResolver(zeusConfigDirName, prefixedName);

// Check if the prefixed version exists (needs fs import)
const fs = require('fs');
if (fs.existsSync(prefixedPath)) {
return prefixedName;
}
}

return cleanedContractName;
}
export const currentUser = () => execSync('git config --global user.email').toString('utf-8').trim();

export const blankDeploy = (args: {env: string, chainId: number, upgrade: string, upgradePath: string, name: string, segments: Segment[]}) => {
Expand Down
15 changes: 12 additions & 3 deletions src/commands/deploy/cmd/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { loadExistingEnvs } from "../../env/cmd/list";
import { execSync } from "child_process";
import ora from "ora";
import { rpcUrl } from "../../prompts";
import { getActiveDeploy } from "./utils";
import { getActiveDeploy, resolveArtifactName } from "./utils";
import * as AllChains from "viem/chains";
import { canonicalPaths } from "../../../metadata/paths";
import { TForgeRequest, TGnosisRequest } from "../../../signing/strategy";
Expand All @@ -16,7 +16,7 @@ import { join } from "path";
import { computeFairHash } from "../utils";
import { getTrace } from "../../../signing/utils";
import chalk from "chalk";
import { readFileSync } from "fs";
import { existsSync, readFileSync } from "fs";
import { configs } from "../../configs";
import EOASigningStrategy from "../../../signing/strategies/eoa/privateKey";
import { GnosisEOAApiStrategy } from "../../../signing/strategies/gnosis/api/gnosisEoa";
Expand Down Expand Up @@ -119,8 +119,17 @@ async function handler(_user: TState, args: {env: string, deploy: string | undef
const onchainBytecode: Record<string, `0x${string}`> = {};

const zeusConfigDirName = await configs.zeus.dirname();
const prefix = deployedContracts._?.prefix;

const contractMetadata = Object.fromEntries(deployedContracts._.contracts.map(contract => {
const metadata = JSON.parse(readFileSync(canonicalPaths.contractJson(zeusConfigDirName, cleanContractName(contract.contract)), 'utf-8')) as ForgeSolidityMetadata;
const finalContractName = resolveArtifactName(
contract.contract,
prefix,
zeusConfigDirName,
canonicalPaths.contractJson
);

const metadata = JSON.parse(readFileSync(canonicalPaths.contractJson(zeusConfigDirName, finalContractName), 'utf-8')) as ForgeSolidityMetadata;
return [contract.contract, metadata];
}));

Expand Down
72 changes: 68 additions & 4 deletions src/commands/env/cmd/show.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {command} from 'cmd-ts';
import {command, flag} from 'cmd-ts';
import {json} from '../../args';
import { assertInRepo, withHost, requires, TState } from '../../inject';
import * as allArgs from '../../args';
Expand All @@ -7,8 +7,12 @@ import { loadExistingEnvs } from './list';
import { injectableEnvForEnvironment } from '../../run';
import { getActiveDeploy } from '../../deploy/cmd/utils';

const pretty = flag({
long: 'pretty',
description: 'Display contracts with proxy and implementation addresses in a single row'
});

export async function handler(_user: TState, args: {json: boolean |undefined, env: string, pending: boolean}): Promise<void> {
export async function handler(_user: TState, args: {json: boolean |undefined, env: string, pending: boolean, pretty: boolean}): Promise<void> {
const user = assertInRepo(_user);
const txn = await user.metadataStore.begin({verbose: true});
const envs = await loadExistingEnvs(txn);
Expand All @@ -23,14 +27,68 @@ export async function handler(_user: TState, args: {json: boolean |undefined, en

const preEnv = await injectableEnvForEnvironment(txn, args.env);
const env = await injectableEnvForEnvironment(txn, args.env, withDeploy?._.name);

if (args.json) {
console.log(JSON.stringify(env))
} else if (args.pretty) {
console.log(chalk.bold.underline(`Environment Parameters`))

// Separate contracts from other env variables
const contractEntries: Record<string, {proxy?: string, impl?: string}> = {};
const otherEntries: Record<string, string> = {};

const sortedKeys = Object.keys(env).sort();

for (const key of sortedKeys) {
if (key.startsWith('ZEUS_DEPLOYED_')) {
const contractName = key.replace('ZEUS_DEPLOYED_', '');

if (contractName.endsWith('_Proxy')) {
const baseName = contractName.substring(0, contractName.length - '_Proxy'.length);
if (!contractEntries[baseName]) {
contractEntries[baseName] = {};
}
contractEntries[baseName].proxy = env[key];
} else if (contractName.endsWith('_Impl')) {
const baseName = contractName.substring(0, contractName.length - '_Impl'.length);
if (!contractEntries[baseName]) {
contractEntries[baseName] = {};
}
contractEntries[baseName].impl = env[key];
} else {
// No proxy/impl suffix, treat as implementation only
if (!contractEntries[contractName]) {
contractEntries[contractName] = {};
}
contractEntries[contractName].impl = env[key];
}
} else {
otherEntries[key] = env[key];
}
}

// Display contracts
if (Object.keys(contractEntries).length > 0) {
console.log(chalk.bold('\nContracts:'));
const contractTable = Object.entries(contractEntries).map(([name, addresses]) => ({
contract: name,
proxy: addresses.proxy || '-',
implementation: addresses.impl || '-'
}));
console.table(contractTable);
}

// Display other environment variables
if (Object.keys(otherEntries).length > 0) {
console.log(chalk.bold('\nEnvironment Variables:'));
console.table(otherEntries);
}
} else {
console.log(chalk.bold.underline(`Environment Parameters`))

// highlight any parameters that have changed.
if (withDeploy) {
const keys = Object.keys(env);
const keys = Object.keys(env).sort();
interface Item {
name: string,
value: string,
Expand All @@ -47,7 +105,12 @@ export async function handler(_user: TState, args: {json: boolean |undefined, en
}
console.table(printableEnv);
} else {
console.table(env);
const sortedKeys = Object.keys(env).sort();
const sortedEnv = sortedKeys.reduce((acc, key) => {
acc[key] = env[key];
return acc;
}, {} as Record<string, string>);
console.table(sortedEnv);
}
}
}
Expand All @@ -60,6 +123,7 @@ const cmd = command({
pending: allArgs.pending,
env: allArgs.envPositional,
json,
pretty,
},
handler: requires(handler, withHost),
})
Expand Down
37 changes: 37 additions & 0 deletions src/commands/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@

import { select as inquirerSelect, Separator, input, password as inquirerPassword } from '@inquirer/prompts';
import { exec } from 'child_process';
import { compare } from 'compare-versions';
import chalk from 'chalk';
import { configs } from './configs';
import pkgInfo from '../../package.json';

const HOURS = (1000) * (60) * (60);

interface Choice<T> {
name: string;
value: T;
description?: string;
}

// Fire-and-forget update check (non-blocking)
export const checkForUpdates = async () => {
try {
const zeusProfile = await configs.zeusProfile.load();
if (zeusProfile?.lastUpdateCheck === undefined || Date.now() - zeusProfile?.lastUpdateCheck > (3 * HOURS)) {
exec(`npm view ${pkgInfo.name} version`, { timeout: 5000 }, (error, stdout) => {
if (error) return; // Silently fail on network issues

const latestRemoteVersion = stdout.toString().trim();
const currentVersion = pkgInfo.version;

if (compare(latestRemoteVersion, currentVersion, '>')) {
console.log(chalk.yellow(`==================================================`))
console.log(chalk.yellow(`A new version (${latestRemoteVersion}) is available!\n`))
console.log(chalk.bold.yellow(`\tnpm install -g @layr-labs/zeus`))
console.log(chalk.yellow(`==================================================`))
}

configs.zeusProfile.write({
...zeusProfile,
lastUpdateCheck: Date.now()
}).catch(() => {
// Silently fail
});
});
}
} catch {
// Silently fail - don't block on update check
}
};

export const select = async <T>(args: {
prompt: string,
choices: (Choice<T> | Separator)[]
Expand Down
21 changes: 18 additions & 3 deletions src/deploy/handlers/eoa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { join } from "path";
import ora from "ora";
import { runTest } from "../../signing/strategies/test";
import { canonicalPaths } from "../../metadata/paths";
import { advance, cleanContractName, sleepMs } from "../../commands/deploy/cmd/utils";
import { advance, cleanContractName, resolveArtifactName, sleepMs } from "../../commands/deploy/cmd/utils";
import chalk from "chalk";
import { wouldYouLikeToContinue } from "../../commands/prompts";
import { configs } from "../../commands/configs";
Expand Down Expand Up @@ -125,10 +125,25 @@ export async function executeEOAPhase(deploy: SavebleDocument<TDeploy>, metatxn:

// look up any contracts compiled and their associated bytecode.
const zeusConfigDirName = await configs.zeus.dirname();

// Load the deployed contracts manifest to check for prefix
const deployedContractsManifest = await metatxn.getJSONFile<TDeployedContractsManifest>(
canonicalPaths.deployDeployedContracts(deploy._)
);
const prefix = deployedContractsManifest._?.prefix;

const withDeployedBytecodeHashes = await Promise.all(sigRequest.deployedContracts?.map(async (contract) => {
const contractInfo = JSON.parse(readFileSync(canonicalPaths.contractInformation(zeusConfigDirName, cleanContractName(contract.contract)), 'utf-8')) as ForgeSolidityMetadata;
const finalContractName = resolveArtifactName(
contract.contract,
prefix,
zeusConfigDirName,
canonicalPaths.contractInformation
);

const contractJsonPath = canonicalPaths.contractInformation(zeusConfigDirName, finalContractName);
const contractInfo = JSON.parse(readFileSync(contractJsonPath, 'utf-8')) as ForgeSolidityMetadata;
// save the contract abi.
const segmentAbi = await metatxn.getJSONFile<ForgeSolidityMetadata>(canonicalPaths.segmentContractAbi({...deploy._, contractName: cleanContractName(contract.contract)}))
const segmentAbi = await metatxn.getJSONFile<ForgeSolidityMetadata>(canonicalPaths.segmentContractAbi({...deploy._, contractName: finalContractName}))
segmentAbi._ = contractInfo;
await segmentAbi.save();
return {
Expand Down
21 changes: 18 additions & 3 deletions src/deploy/handlers/gnosis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { HaltDeployError, PauseDeployError, TGnosisRequest, TStrategyOptions } f
import { GnosisSigningStrategy } from "../../signing/strategies/gnosis/gnosis";
import { GnosisOnchainEOAStrategy } from "../../signing/strategies/gnosis/onchain/onchainEoa";
import { MultisigMetadata, TDeploy, TDeployStateMutations, TMutation, TTestOutput, TDeployedContractsManifest, ForgeSolidityMetadata } from "../../metadata/schema";
import { advance, advanceSegment, getChain, isTerminalPhase, cleanContractName } from "../../commands/deploy/cmd/utils";
import { advance, advanceSegment, getChain, isTerminalPhase, cleanContractName, resolveArtifactName } from "../../commands/deploy/cmd/utils";
import { injectableEnvForEnvironment } from "../../commands/run";
import { canonicalPaths } from "../../metadata/paths";
import { multisigBaseUrl, overrideTxServiceUrlForChainId } from "../../signing/strategies/gnosis/api/utils";
Expand Down Expand Up @@ -126,10 +126,25 @@ export async function executeMultisigPhase(deploy: SavebleDocument<TDeploy>, met
// Handle deployed contracts from ZeusDeploy events
if (sigRequest.deployedContracts && sigRequest.deployedContracts.length > 0) {
const zeusConfigDirName = await configs.zeus.dirname();

// Load the deployed contracts manifest to check for prefix
const deployedContractsManifest = await metatxn.getJSONFile<TDeployedContractsManifest>(
canonicalPaths.deployDeployedContracts(deploy._)
);
const prefix = deployedContractsManifest._?.prefix;

const withDeployedBytecodeHashes = await Promise.all(sigRequest.deployedContracts.map(async (contract) => {
const contractInfo = JSON.parse(readFileSync(canonicalPaths.contractInformation(zeusConfigDirName, cleanContractName(contract.contract)), 'utf-8')) as ForgeSolidityMetadata;
const finalContractName = resolveArtifactName(
contract.contract,
prefix,
zeusConfigDirName,
canonicalPaths.contractInformation
);

const contractJsonPath = canonicalPaths.contractInformation(zeusConfigDirName, finalContractName);
const contractInfo = JSON.parse(readFileSync(contractJsonPath, 'utf-8')) as ForgeSolidityMetadata;
// save the contract abi.
const segmentAbi = await metatxn.getJSONFile<ForgeSolidityMetadata>(canonicalPaths.segmentContractAbi({...deploy._, contractName: cleanContractName(contract.contract)}))
const segmentAbi = await metatxn.getJSONFile<ForgeSolidityMetadata>(canonicalPaths.segmentContractAbi({...deploy._, contractName: finalContractName}))
segmentAbi._ = contractInfo;
await segmentAbi.save();
return {
Expand Down
Loading
Loading