Skip to content

Commit d913626

Browse files
authored
[1.3.0] Minor Zeus Upgrade (#29)
* zeus 1.3.0 * update changelog * save * Save
1 parent 518dff2 commit d913626

File tree

18 files changed

+302
-144
lines changed

18 files changed

+302
-144
lines changed

.github/workflows/ci-pipeline.yml

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
steps:
1616
- name: Checkout code
1717
uses: actions/checkout@v3
18+
- name: Install deps
19+
run: sudo apt-get install libusb-1.0-0-dev libudev-dev
1820
- name: Setup Node.js
1921
uses: actions/setup-node@v3
2022
with:
@@ -30,6 +32,8 @@ jobs:
3032
steps:
3133
- name: Checkout code
3234
uses: actions/checkout@v3
35+
- name: Install deps
36+
run: sudo apt-get install libusb-1.0-0-dev libudev-dev
3337
- name: Setup Node.js
3438
uses: actions/setup-node@v3
3539
with:
@@ -45,6 +49,8 @@ jobs:
4549
steps:
4650
- name: Checkout code
4751
uses: actions/checkout@v3
52+
- name: Install deps
53+
run: sudo apt-get install libusb-1.0-0-dev libudev-dev
4854
- name: Setup Node.js
4955
uses: actions/setup-node@v3
5056
with:
@@ -60,6 +66,8 @@ jobs:
6066
steps:
6167
- name: Checkout code
6268
uses: actions/checkout@v3
69+
- name: Install deps
70+
run: sudo apt-get install libusb-1.0-0-dev libudev-dev
6371
- name: Setup Node.js
6472
uses: actions/setup-node@v3
6573
with:

CHANGELOG.md

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11

22
**[Current]**
3+
1.3.0:
4+
**Bugs**
5+
- Fixes a bug where deploying Beacon contracts did not work.
6+
- Fixed a bug where providing an incorrect RPC node lead to an inf loop
7+
- Changes the calculation logic of multisig transactions to be more accurate.
8+
- Fixed an issue where `zeus deploy verify` did not respect the deploy lock, leading to clobbers.
9+
10+
**Changes**
11+
- Removed `--slow` from forge invocations, as zeus already awaits individual transactions.
12+
13+
**Features**
14+
- Allows multisig steps to not return a transaction without breaking the deploy.
15+
- Support non-standard ChainIDs
16+
17+
318
1.2.3:
419
- Zeus automatically displays messages indicating the user should upgrade now.
520

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@layr-labs/zeus",
3-
"version": "1.2.3",
3+
"version": "1.3.0",
44
"description": "web3 deployer / metadata manager",
55
"main": "src/index.ts",
66
"scripts": {

src/commands/deploy/cmd/run.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export async function handler(_user: TState, args: {env: string, resume: boolean
175175
if (deploy) {
176176
if (args.upgrade || !args.resume) {
177177
console.error(`Existing deploy in progress. Please rerun with --resume (and not --upgrade, as the current upgrade is ${deploy._.upgrade}).`)
178-
console.error(`\t\tzeus deploy run --resume --env ${deploy._.env}`)
178+
console.error(chalk.bold(`\n\t\tzeus deploy run --resume --env ${deploy._.env}`))
179179
return;
180180
}
181181

src/commands/deploy/cmd/utils-locks.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ export const releaseDeployLock: (deploy: TDeploy, txn: Transaction) => Promise<v
2929
const SECONDS = 1000;
3030
const MINUTES = 60 * SECONDS;
3131

32-
export const acquireDeployLock: (deploy: TDeploy, txn: Transaction) => Promise<boolean> = async (deploy, txn) => {
32+
export const acquireDeployLock: (deploy: TDeploy, txn: Transaction, message?: string) => Promise<boolean> = async (deploy, txn, message = '') => {
3333
const prompt = ora(`Acquiring deploy lock...'`);
3434
const spinner = prompt.start();
3535
try {
3636
const deployLock = await txn.getJSONFile<TDeployLock>(canonicalPaths.deployLock(deploy));
3737
const currentEmail = currentUser();
3838

3939
const acquireLock = async () => {
40-
deployLock._.description = `Deploy ${deploy.name} - ${deploy.segmentId}/${deploy.phase}`;
40+
deployLock._.description = `Deploy ${deploy.name} - ${deploy.segmentId}/${deploy.phase}${message !== '' ? `(${message})` : ``}`;
4141
deployLock._.holder = currentEmail;
4242
deployLock._.untilTimestampMs = Date.now() + (5 * MINUTES);
4343
await deployLock.save();

src/commands/deploy/cmd/utils.ts

+3
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ export const cleanContractName = (contractName: string) => {
8080
return contractName.substring(0, contractName.length - `_Impl`.length);
8181
} else if (contractName.endsWith('_Proxy')) {
8282
return contractName.substring(0, contractName.length - `_Proxy`.length);
83+
} else if (contractName.endsWith('_Beacon')) {
84+
return contractName.substring(0, contractName.length - `_Beacon`.length);
8385
}
86+
8487
return contractName;
8588
}
8689
export const currentUser = () => execSync('git config --global user.email').toString('utf-8').trim();

src/commands/deploy/cmd/verify.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { getTrace } from "../../../signing/utils";
1818
import chalk from "chalk";
1919
import { readFileSync } from "fs";
2020
import { getRepoRoot } from "../../configs";
21+
import { acquireDeployLock, releaseDeployLock } from "./utils-locks";
2122

2223
const currentUser = () => execSync('git config --global user.email').toString('utf-8').trim();
2324

@@ -34,7 +35,9 @@ const cleanContractName = (contract: string) => {
3435
return contract.substring(0, contract.length - `_Impl`.length);
3536
} else if (contract.endsWith('_Proxy')) {
3637
return contract.substring(0, contract.length - `_Proxy`.length);
37-
}
38+
} else if (contract.endsWith('_Beacon')) {
39+
return contract.substring(0, contract.length - `_Beacon`.length);
40+
}
3841
return contract;
3942
}
4043

@@ -44,6 +47,8 @@ const shortenHex = (str: string) => {
4447

4548
async function handler(_user: TState, args: {env: string}) {
4649
try {
50+
let willSaveVerification = false;
51+
4752
const user = assertInRepo(_user);
4853
const metatxn = await user.metadataStore.begin();
4954
const envs = await loadExistingEnvs(metatxn);
@@ -57,6 +62,13 @@ async function handler(_user: TState, args: {env: string}) {
5762
console.error(`No active deploy to verify.`);
5863
return;
5964
}
65+
try {
66+
if (await acquireDeployLock(deploy._, metatxn,'verifying deploy')) {
67+
willSaveVerification = true;
68+
}
69+
} catch {
70+
//
71+
}
6072

6173
const customRpcUrl = await rpcUrl(deploy._.chainId);
6274

@@ -245,8 +257,15 @@ async function handler(_user: TState, args: {env: string}) {
245257
}
246258

247259
try {
248-
await deployedContracts.save();
249-
await metatxn.commit(`[${args.env}] verify deploy ${deploy._.name} - ${isFailure ? 'failure' : 'success'}`)
260+
if (willSaveVerification) {
261+
await deployedContracts.save();
262+
await metatxn.commit(`[${args.env}] verify deploy ${deploy._.name} - ${isFailure ? 'failure' : 'success'}`)
263+
try {
264+
await releaseDeployLock(deploy._, metatxn);
265+
} catch {
266+
//
267+
}
268+
}
250269
} catch (e) {
251270
console.warn(`Failed to record verification. You may not have write access.`);
252271
console.error(e)

src/commands/env/cmd/new.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,26 @@ async function handler(_user: TState): Promise<void> {
2020
}
2121
});
2222

23-
const chainId = await select({
23+
const _chainId = await select({
2424
prompt: "Chain?",
2525
choices: [
2626
{name: 'Sepolia', value: 0xaa36a7, description: "ETH Sepolia Testnet"},
2727
{name: 'Holesky', value: 0x4268, description: "ETH Holesky Testnet"},
2828
{name: 'Mainnet', value: 0x1, description: "ETH Mainnet"},
29+
{name: 'Custom', value: 0x0, description: "Custom Chain"},
2930
]
3031
});
32+
const chainId = _chainId !== 0 ? _chainId : parseInt(await question({
33+
text: "Chain ID?",
34+
isValid: (text: string) => {
35+
try {
36+
parseInt(text);
37+
return true;
38+
} catch {
39+
return false;
40+
}
41+
}
42+
}))
3143

3244
// Step 2: Create a new folder in the default branch
3345
const envManifestContent: TEnvironmentManifest = {

src/commands/prompts.ts

+7-12
Original file line numberDiff line numberDiff line change
@@ -223,23 +223,16 @@ export const getChainId = async (nodeUrl: string) => {
223223
}
224224

225225
export const chainIdName = (chainId: number) => {
226-
switch (chainId) {
227-
case 1:
228-
return 'Mainnet'
229-
case 17000:
230-
return 'Holesky'
231-
case 11155111:
232-
return 'Sepolia';
233-
default:
234-
return `chains/${chainId}`;
235-
}
226+
const chain = Object.values((AllChains as unknown as AllChains.Chain<undefined>[])).find(chain => chain.id === chainId)
227+
return chain?.name ?? `chains/${chainId}`;
236228
};
237229

238230
export const rpcUrl = async (forChainId: number) => {
231+
let attempt = 0;
239232
while (true) {
240233
const result = await envVarOrPrompt({
241234
title: `Enter an RPC url (or $ENV_VAR) for ${chainIdName(forChainId)}`,
242-
reuseKey: `node-url`,
235+
reuseKey: attempt === 0 ? `node-url` : undefined,
243236
isValid: (text) => {
244237
try {
245238
let url: string = text;
@@ -256,12 +249,14 @@ export const rpcUrl = async (forChainId: number) => {
256249
directEntryInputType: 'text',
257250
envVarSearchMessage: 'Enter a node url'
258251
})
252+
attempt++;
259253

260254
const chainId = await getChainId(result);
261255
if (chainId !== forChainId) {
262-
console.error(`This node is for an incorrect network (expected chainId=${chainIdName(forChainId)}, got ${chainIdName(chainId)})`);
256+
console.error(chalk.red(`This node is for an incorrect network (expected chainId=${chainIdName(forChainId)}, got ${chainIdName(chainId)})\n\n`));
263257
continue;
264258
}
259+
265260
return result;
266261
}
267262
}

src/deploy/handlers/gnosis.ts

+64-23
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,34 @@ export async function executeMultisigPhase(deploy: SavebleDocument<TDeploy>, met
5959

6060
multisigStrategy = multisigStrategy ?? (await promptForStrategy(deploy, metatxn) as GnosisSigningStrategy);
6161
const sigRequest = await multisigStrategy.requestNew(script, deploy._) as TGnosisRequest;
62-
deploy._.metadata[deploy._.segmentId] = {
63-
type: "multisig",
64-
signer: sigRequest.senderAddress,
65-
signerType: multisigStrategy.id,
66-
gnosisTransactionHash: sigRequest.safeTxHash as `0x${string}`,
67-
gnosisCalldata: undefined, // ommitting this so that a third party can't execute immediately.
68-
multisig: sigRequest.safeAddress,
69-
confirmed: false,
70-
cancellationTransactionHash: undefined
71-
};
72-
73-
if (sigRequest.immediateExecution && !sigRequest.immediateExecution.success) {
74-
console.error(`This strategy attempted to immediately execute your request. It was unsuccessful. (${sigRequest.immediateExecution.transaction})`);
75-
throw new HaltDeployError(deploy, `the onchain execution failed: ${JSON.stringify(sigRequest.immediateExecution, null, 2)}`)
62+
if (sigRequest.empty) {
63+
deploy._.metadata[deploy._.segmentId] = {
64+
type: "multisig",
65+
signer: sigRequest.senderAddress,
66+
signerType: multisigStrategy.id,
67+
gnosisTransactionHash: sigRequest.safeTxHash as `0x${string}`,
68+
gnosisCalldata: undefined, // ommitting this so that a third party can't execute immediately.
69+
multisig: sigRequest.safeAddress,
70+
confirmed: true,
71+
cancellationTransactionHash: undefined
72+
}
73+
console.log(chalk.bold.underline(`This script did not output a transaction. Will be skipped.`));
74+
} else {
75+
deploy._.metadata[deploy._.segmentId] = {
76+
type: "multisig",
77+
signer: sigRequest.senderAddress,
78+
signerType: multisigStrategy.id,
79+
gnosisTransactionHash: sigRequest.safeTxHash as `0x${string}`,
80+
gnosisCalldata: undefined, // ommitting this so that a third party can't execute immediately.
81+
multisig: sigRequest.safeAddress,
82+
confirmed: false,
83+
cancellationTransactionHash: undefined
84+
};
85+
86+
if (sigRequest.immediateExecution && !sigRequest.immediateExecution.success) {
87+
console.error(`This strategy attempted to immediately execute your request. It was unsuccessful. (${sigRequest.immediateExecution.transaction})`);
88+
throw new HaltDeployError(deploy, `the onchain execution failed: ${JSON.stringify(sigRequest.immediateExecution, null, 2)}`)
89+
}
7690
}
7791

7892
if (sigRequest.stateUpdates && Object.keys(sigRequest.stateUpdates).length > 0) {
@@ -100,13 +114,20 @@ export async function executeMultisigPhase(deploy: SavebleDocument<TDeploy>, met
100114
multisigRun._ = sigRequest;
101115
await multisigRun.save();
102116

103-
if (sigRequest.immediateExecution && sigRequest.immediateExecution.transaction) {
117+
if (sigRequest.empty) {
118+
console.log(`No transaction, skipping forward.`);
119+
await advance(deploy);
120+
await deploy.save();
121+
await metatxn.commit(`[deploy ${deploy._.name}] multisig step did not output a transaction`);
122+
123+
} else if (sigRequest.immediateExecution && sigRequest.immediateExecution.transaction) {
104124
(deploy._.metadata[deploy._.segmentId] as MultisigMetadata).confirmed = true;
105125
(deploy._.metadata[deploy._.segmentId] as MultisigMetadata).immediateExecutionHash = sigRequest.immediateExecution.transaction;
106126
console.log(`Transaction recorded: ${sigRequest.immediateExecution.transaction}`)
107127
await advanceSegment(deploy);
108128
await deploy.save();
109129
await metatxn.commit(`[deploy ${deploy._.name}] multisig transaction completed instantly`);
130+
110131
} else {
111132
await advance(deploy);
112133
await deploy.save();
@@ -122,11 +143,20 @@ export async function executeMultisigPhase(deploy: SavebleDocument<TDeploy>, met
122143
const multisigDeploy = await metatxn.getJSONFile<TGnosisRequest>(
123144
canonicalPaths.multisigRun({deployEnv: deploy._.env, deployName: deploy._.name, segmentId: deploy._.segmentId})
124145
)
146+
const safeTxHash = multisigDeploy._.safeTxHash;
147+
if (!safeTxHash) {
148+
console.log(`No multisig tx hash found, skipping forward.`)
149+
await advanceSegment(deploy);
150+
await deploy.save();
151+
await metatxn.commit(`[deploy ${deploy._.name}] multisig step did not output a transaction`);
152+
return;
153+
}
154+
125155
const safeApi = new SafeApiKit({chainId: BigInt(deploy._.chainId), txServiceUrl: overrideTxServiceUrlForChainId(deploy._.chainId)})
126-
const multisigTxn = await safeApi.getTransaction(multisigDeploy._.safeTxHash);
156+
const multisigTxn = await safeApi.getTransaction(safeTxHash);
127157

128158
if (multisigTxn.confirmations?.length === multisigTxn.confirmationsRequired) {
129-
console.log(chalk.green(`SafeTxn(${multisigDeploy._.safeTxHash}): ${multisigTxn.confirmations?.length}/${multisigTxn.confirmationsRequired} confirmations received!`))
159+
console.log(chalk.green(`SafeTxn(${safeTxHash}): ${multisigTxn.confirmations?.length}/${multisigTxn.confirmationsRequired} confirmations received!`))
130160
await advance(deploy);
131161
await deploy.save();
132162
await metatxn.commit(`[deploy ${deploy._.name}] multisig transaction signers found`);
@@ -144,28 +174,36 @@ export async function executeMultisigPhase(deploy: SavebleDocument<TDeploy>, met
144174
const multisigDeploy = await metatxn.getJSONFile<TGnosisRequest>(
145175
canonicalPaths.multisigRun({deployEnv: deploy._.env, deployName: deploy._.name, segmentId: deploy._.segmentId})
146176
)
177+
const safeTxHash = multisigDeploy._.safeTxHash;
178+
if (!safeTxHash) {
179+
console.log(`No multisig tx hash found, skipping forward.`)
180+
await advanceSegment(deploy);
181+
await deploy.save();
182+
await metatxn.commit(`[deploy ${deploy._.name}] multisig step did not output a transaction`);
183+
return;
184+
}
147185
const safeApi = new SafeApiKit({chainId: BigInt(deploy._.chainId), txServiceUrl: overrideTxServiceUrlForChainId(deploy._.chainId)})
148-
const multisigTxn = await safeApi.getTransaction(multisigDeploy._.safeTxHash);
186+
const multisigTxn = await safeApi.getTransaction(safeTxHash);
149187

150188
const multisigTxnPersist = await metatxn.getJSONFile(canonicalPaths.multisigTransaction({deployEnv: deploy._.env, deployName: deploy._.name, segmentId: deploy._.segmentId}))
151189
multisigTxnPersist._ = multisigTxn;
152190
await multisigTxnPersist.save();
153191

154192
if (!multisigTxn.isExecuted) {
155-
console.log(chalk.cyan(`SafeTxn(${multisigDeploy._.safeTxHash}): still waiting for execution.`))
193+
console.log(chalk.cyan(`SafeTxn(${safeTxHash}): still waiting for execution.`))
156194
console.error(`\tShare the following URI: ${multisigBaseUrl(deploy._.chainId)}/transactions/queue?safe=${multisigDeploy._.safeAddress}`)
157195
console.error(`Resume deploy with: `)
158196
console.error(`\t\tzeus deploy run --resume --env ${deploy._.env}`);
159197
await metatxn.commit(`[deploy ${deploy._.name}] multisig transaction awaiting execution`);
160198
throw new HaltDeployError(deploy, `Waiting on multisig transaction execution.`);
161199
} else if (!multisigTxn.isSuccessful) {
162-
console.log(chalk.red(`SafeTxn(${multisigDeploy._.safeTxHash}): failed onchain. Failing deploy.`))
200+
console.log(chalk.red(`SafeTxn(${safeTxHash}): failed onchain. Failing deploy.`))
163201
deploy._.phase = 'failed';
164202
await deploy.save();
165203
await metatxn.commit(`[deploy ${deploy._.name}] multisig transaction failed`);
166204
throw new HaltDeployError(deploy, `Multisig transaction failed.`);
167205
} else {
168-
console.log(chalk.green(`SafeTxn(${multisigDeploy._.safeTxHash}): executed (${multisigTxn.transactionHash})`))
206+
console.log(chalk.green(`SafeTxn(${safeTxHash}): executed (${multisigTxn.transactionHash})`))
169207
await advance(deploy);
170208
await deploy.save();
171209
await metatxn.commit(`[deploy ${deploy._.name}] multisig transaction executed`);
@@ -178,8 +216,11 @@ export async function executeMultisigPhase(deploy: SavebleDocument<TDeploy>, met
178216
)
179217

180218
if (!multisigTxn || !multisigTxn._ || !multisigTxn._.transactionHash) {
181-
console.error(`Deploy missing multisig transaction data.`);
182-
throw new HaltDeployError(deploy, `Zeus script outputted no multisig transactions.`);
219+
console.log(`No multisig tx hash found, skipping forward.`)
220+
await advanceSegment(deploy);
221+
await deploy.save();
222+
await metatxn.commit(`[deploy ${deploy._.name}] multisig step did not output a transaction`);
223+
return;
183224
}
184225

185226
if (multisigTxn._.executionDate && multisigTxn._.transactionHash) {

src/metadata/clone/LocalCloneMetadataStore.ts

-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ export class LocalCloneMetadataStore implements MetadataStore {
3939
throw new Error('Local repository not initialized. Call initialize() first.');
4040
}
4141

42-
if (options?.verbose) {
43-
console.log(`Starting transaction in: ${this.localPath}`);
44-
}
45-
4642
return new LocalCloneTransaction(this.localPath, !!options?.verbose);
4743
}
4844
}

0 commit comments

Comments
 (0)