Skip to content

Commit 6bd1b82

Browse files
authored
[CLI] Publish and deploy updates for ZKSync (#2584)
1 parent 052892b commit 6bd1b82

File tree

16 files changed

+415
-54
lines changed

16 files changed

+415
-54
lines changed

.changeset/brave-pandas-whisper.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@thirdweb-dev/cli": patch
3+
"@thirdweb-dev/sdk": patch
4+
---
5+
6+
cli changes for zksync publish

legacy_packages/cli/src/cli/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ const main = async () => {
377377
.option("--dry-run", "dry run (skip actually publishing)")
378378
.option("-d, --debug", "show debug logs")
379379
.option("--ci", "Continuous Integration mode")
380+
.option("--zksync", "Publish with ZKSync settings")
380381
.option(
381382
"--link-lib <library:address...>",
382383
"Specify library names and addresses",

legacy_packages/cli/src/common/processor.ts

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,7 @@ export async function processProject(
6161

6262
logger.debug("Processing project at path " + projectPath);
6363

64-
const projectType = options.zksync
65-
? "hardhat"
66-
: await detect(projectPath, options);
64+
const projectType = await detect(projectPath, options);
6765

6866
if (projectType === "none") {
6967
if (command === "deploy") {
@@ -106,9 +104,25 @@ export async function processProject(
106104
}
107105

108106
let compiledResult: { contracts: ContractPayload[] };
107+
let zkCompiledResult: { contracts: ContractPayload[] };
109108
const compileLoader = spinner("Compiling project...");
110109
try {
111110
compiledResult = await build(projectPath, projectType, options);
111+
112+
if (options.zksync) {
113+
zkCompiledResult = await build(projectPath, "zk-hardhat", options);
114+
115+
if (
116+
compiledResult.contracts.length !== zkCompiledResult.contracts.length
117+
) {
118+
logger.error(
119+
"Length mismatch: zksolc and solc compiled contracts differ.",
120+
);
121+
process.exit(1);
122+
}
123+
} else {
124+
zkCompiledResult = { contracts: [] };
125+
}
112126
} catch (e) {
113127
compileLoader.fail("Compilation failed");
114128
logger.error(e);
@@ -223,6 +237,23 @@ export async function processProject(
223237
process.exit(1);
224238
}
225239

240+
let zkSelectedContracts: ContractPayload[] = [];
241+
if (options.zksync) {
242+
const fileNames = selectedContracts.map((selected) => selected.fileName);
243+
zkSelectedContracts = zkCompiledResult.contracts.filter((contract) => {
244+
const index = fileNames.indexOf(contract.fileName);
245+
246+
return index !== -1;
247+
});
248+
249+
if (zkSelectedContracts.length !== selectedContracts.length) {
250+
info(
251+
"Selected contract not present in zksolc compiled contracts. Aborting.",
252+
);
253+
process.exit(1);
254+
}
255+
}
256+
226257
if (options.dryRun) {
227258
info("Dry run, skipping deployment");
228259
process.exit(0);
@@ -332,6 +363,81 @@ export async function processProject(
332363
metadataUri: metadataURIs[i],
333364
bytecodeUri: bytecodeURIs[i],
334365
analytics,
366+
compilers: {
367+
solc: [
368+
{
369+
compilerVersion: compiledResult.contracts[0].compilerVersion,
370+
evmVersion: compiledResult.contracts[0].evmVersion,
371+
metadataUri: metadataURIs[i],
372+
bytecodeUri: bytecodeURIs[i],
373+
},
374+
],
375+
},
376+
};
377+
});
378+
}
379+
380+
// upload zk-contracts if present
381+
if (zkSelectedContracts.length > 0) {
382+
for (let i = 0; i < zkSelectedContracts.length; i++) {
383+
const contract = zkSelectedContracts[i];
384+
if (contract.sources) {
385+
// upload sources in batches to avoid getting rate limited (needs to be single uploads)
386+
const batchSize = 3;
387+
for (let j = 0; j < contract.sources.length; j = j + batchSize) {
388+
const batch = contract.sources.slice(j, j + batchSize);
389+
logger.debug(`Uploading Sources:\n${batch.join("\n")}\n`);
390+
await Promise.all(
391+
batch.map(async (c) => {
392+
const file = readFileSync(c, "utf-8");
393+
if (file.includes(soliditySDKPackage)) {
394+
usesSoliditySDK = true;
395+
}
396+
return await storage.upload(file, {
397+
uploadWithoutDirectory: true,
398+
});
399+
}),
400+
);
401+
}
402+
}
403+
}
404+
405+
// Upload build output metadatas (need to be single uploads)
406+
const zkMetadataURIs = await Promise.all(
407+
zkSelectedContracts.map(async (c) => {
408+
logger.debug(`Uploading ${c.name}...`);
409+
410+
return await storage.upload(JSON.parse(JSON.stringify(c.metadata)), {
411+
uploadWithoutDirectory: true,
412+
});
413+
}),
414+
);
415+
416+
// Upload batch all bytecodes
417+
const zkBytecodes = zkSelectedContracts.map((c) => c.bytecode);
418+
const zkBytecodeURIs = await storage.uploadBatch(zkBytecodes);
419+
420+
combinedContents = combinedContents.map((c, i) => {
421+
return {
422+
...c,
423+
compilers: {
424+
zksolc: [
425+
{
426+
compilerVersion: zkCompiledResult.contracts[0].compilerVersion,
427+
evmVersion: zkCompiledResult.contracts[0].evmVersion,
428+
metadataUri: zkMetadataURIs[i],
429+
bytecodeUri: zkBytecodeURIs[i],
430+
},
431+
],
432+
solc: [
433+
{
434+
compilerVersion: compiledResult.contracts[0].compilerVersion,
435+
evmVersion: compiledResult.contracts[0].evmVersion,
436+
metadataUri: metadataURIs[i],
437+
bytecodeUri: bytecodeURIs[i],
438+
},
439+
],
440+
},
335441
};
336442
});
337443
}
@@ -362,8 +468,8 @@ export function getUrl(hashes: string[], command: string) {
362468
if (hashes.length === 1) {
363469
url = new URL(
364470
THIRDWEB_URL +
365-
`/contracts/${command}/` +
366-
encodeURIComponent(hashes[0].replace("ipfs://", "")),
471+
`/contracts/${command}/` +
472+
encodeURIComponent(hashes[0].replace("ipfs://", "")),
367473
);
368474
} else {
369475
url = new URL(THIRDWEB_URL + "/contracts/" + command);

legacy_packages/cli/src/core/builder/brownie.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export class BrownieBuilder extends BaseBuilder {
5656
sources: [], // TODO
5757
fileName: "", // TODO
5858
name: contractName,
59+
compilerVersion: "",
60+
evmVersion: "",
5961
});
6062
break;
6163
}

legacy_packages/cli/src/core/builder/build.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { FoundryBuilder } from "./foundry";
55
import { HardhatBuilder } from "./hardhat";
66
import { SolcBuilder } from "./solc";
77
import { TruffleBuilder } from "./truffle";
8+
import { ZKHardhatBuilder } from "./zkHardhat";
89

910
export default async function build(
1011
path: string,
@@ -19,6 +20,10 @@ export default async function build(
1920
builder = new HardhatBuilder();
2021
break;
2122
}
23+
case "zk-hardhat": {
24+
builder = new ZKHardhatBuilder();
25+
break;
26+
}
2227
case "foundry": {
2328
builder = new FoundryBuilder();
2429
break;

legacy_packages/cli/src/core/builder/foundry.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export class FoundryBuilder extends BaseBuilder {
107107
contractInfo.rawMetadata ||
108108
this.sanitizeParsedMetadata(parsedMetadata, contractInfo.abi);
109109

110+
const evmVersion = metadata.settings?.evmVersion || "";
111+
const compilerVersion = metadata.compiler?.version || "";
112+
110113
const sources = Object.keys(parsedMetadata.sources)
111114
.map((path) => {
112115
if (path.startsWith("/") && existsSync(path)) {
@@ -146,6 +149,8 @@ export class FoundryBuilder extends BaseBuilder {
146149
bytecode,
147150
fileName,
148151
sources,
152+
compilerVersion,
153+
evmVersion,
149154
});
150155
}
151156
}

legacy_packages/cli/src/core/builder/hardhat.ts

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,7 @@ export class HardhatBuilder extends BaseBuilder {
3636
logger.debug("successfully extracted hardhat config", actualHardhatConfig);
3737

3838
await execute("npx hardhat clean", options.projectPath);
39-
40-
let ignoreIpfsHash = false;
41-
if (options.zksync) {
42-
const zkNetwork = Object.entries(actualHardhatConfig.networks).find(
43-
(network) => {
44-
return (network[1] as any).zksync;
45-
},
46-
);
47-
ignoreIpfsHash = (zkNetwork?.[1] as any).zksync; // IPFS hash can't be recovered from ZKSync bytecode
48-
await execute(
49-
`npx hardhat compile --network ${zkNetwork?.[0]}`,
50-
options.projectPath,
51-
);
52-
} else {
53-
await execute(`npx hardhat compile`, options.projectPath);
54-
}
39+
await execute(`npx hardhat compile`, options.projectPath);
5540

5641
const solcConfigs = actualHardhatConfig.solidity.compilers;
5742
if (solcConfigs) {
@@ -127,17 +112,18 @@ export class HardhatBuilder extends BaseBuilder {
127112
}
128113

129114
let bytecode = info.evm.bytecode.object;
130-
let deployedBytecode = info.evm.deployedBytecode?.object || bytecode;
115+
let deployedBytecode = info.evm.deployedBytecode.object;
131116

132117
// link external libraries if any
133118
bytecode = linkLibrary(bytecode, libraries);
134119
deployedBytecode = linkLibrary(deployedBytecode, libraries);
135120

136121
const { metadata, abi } = info;
137122

138-
const meta = metadata.solc_metadata
139-
? JSON.parse(metadata.solc_metadata)
140-
: JSON.parse(metadata);
123+
const meta = JSON.parse(metadata);
124+
125+
const evmVersion = meta.settings?.evmVersion || "";
126+
const compilerVersion = meta.compiler?.version || "";
141127
const sources = Object.keys(meta.sources)
142128
.map((path) => {
143129
const directPath = join(options.projectPath, path);
@@ -165,20 +151,15 @@ export class HardhatBuilder extends BaseBuilder {
165151
);
166152
const fileName = fileNames.length > 0 ? fileNames[0] : "";
167153

168-
if (
169-
this.shouldProcessContract(
170-
abi,
171-
deployedBytecode,
172-
contractName,
173-
ignoreIpfsHash,
174-
)
175-
) {
154+
if (this.shouldProcessContract(abi, deployedBytecode, contractName)) {
176155
contracts.push({
177-
metadata: JSON.stringify(meta),
156+
metadata,
178157
bytecode,
179158
name: contractName,
180159
fileName,
181160
sources,
161+
compilerVersion,
162+
evmVersion,
182163
});
183164
}
184165
}

legacy_packages/cli/src/core/builder/solc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ export class SolcBuilder extends BaseBuilder {
116116
const parsedMetadata = JSON.parse(metadata);
117117
const abi = parsedMetadata.output.abi;
118118

119+
const evmVersion = parsedMetadata.settings?.evmVersion || "";
120+
const compilerVersion = parsedMetadata.compiler?.version || "";
121+
119122
const target = parsedMetadata.settings.compilationTarget;
120123
if (
121124
Object.keys(target).length === 0 ||
@@ -156,6 +159,8 @@ export class SolcBuilder extends BaseBuilder {
156159
name: contractName,
157160
fileName,
158161
sources: _sources,
162+
compilerVersion,
163+
evmVersion,
159164
});
160165
}
161166
}

legacy_packages/cli/src/core/builder/truffle.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ export class TruffleBuilder extends BaseBuilder {
1313
}> {
1414
// get the current config first
1515
// eslint-disable-next-line @typescript-eslint/no-var-requires
16-
const truffleConfig = require(join(
17-
options.projectPath,
18-
"truffle-config.js",
19-
));
16+
const truffleConfig = require(
17+
join(options.projectPath, "truffle-config.js"),
18+
);
2019

2120
const buildPath = join(
2221
options.projectPath,
@@ -42,6 +41,9 @@ export class TruffleBuilder extends BaseBuilder {
4241
const meta = JSON.parse(metadata);
4342
const abi = meta.output.abi;
4443

44+
const evmVersion = meta.settings?.evmVersion || "";
45+
const compilerVersion = meta.compiler?.version || "";
46+
4547
const target = meta.settings.compilationTarget;
4648
if (
4749
Object.keys(target).length === 0 ||
@@ -81,6 +83,8 @@ export class TruffleBuilder extends BaseBuilder {
8183
name: contractName,
8284
fileName,
8385
sources,
86+
compilerVersion,
87+
evmVersion,
8488
});
8589
}
8690
}

0 commit comments

Comments
 (0)