Skip to content

Commit c67b287

Browse files
authored
Contract verification on zksync (#2481)
1 parent 7ac4e6b commit c67b287

File tree

6 files changed

+241
-3
lines changed

6 files changed

+241
-3
lines changed

.changeset/moody-laws-hang.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@thirdweb-dev/sdk": patch
3+
---
4+
5+
ZkSync contract verification

packages/sdk/src/evm/zksync/constants/addresses.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export const CONTRACT_ADDRESSES: Record<
2525
},
2626
};
2727

28+
export const blockExplorerApiMap: Record<number, string> = {
29+
[324]: "https://block-explorer-api.mainnet.zksync.io/api",
30+
[280]: "https://block-explorer-api.testnets.zksync.dev/api",
31+
[300]: "https://block-explorer-api.sepolia.zksync.dev/api",
32+
};
33+
2834
export const IMPLEMENTATIONS: Record<number, Record<string, string>> = {
2935
// ZKSync Era Goerli Testnet (Deprecated)
3036
[280]: {

packages/sdk/src/evm/zksync/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./zksync-deploy-utils";
2+
export * from "./zksync-verification";
23
export * from "./constants/addresses";

packages/sdk/src/evm/zksync/temp-artifact/TWProxy.ts

Lines changed: 24 additions & 0 deletions
Large diffs are not rendered by default.

packages/sdk/src/evm/zksync/zksync-deploy-utils.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { type BytesLike, Contract, type Signer, utils, Wallet } from "ethers";
1111
import { ThirdwebStorage } from "@thirdweb-dev/storage";
1212
import type { DeployOptions } from "../types/deploy/deploy-options";
1313
import { ThirdwebSDK } from "../core/sdk";
14-
import { getImplementation } from "./constants/addresses";
14+
import { blockExplorerApiMap, getImplementation } from "./constants/addresses";
1515
import { DeploymentTransaction } from "../types/any-evm/deploy-data";
16+
import { zkVerify } from "./zksync-verification";
1617

1718
export async function zkDeployContractFromUri(
1819
publishMetadataUri: string,
@@ -37,6 +38,7 @@ export async function zkDeployContractFromUri(
3738
);
3839
}
3940

41+
let deployedAddress;
4042
if (
4143
extendedMetadata &&
4244
extendedMetadata.factoryDeploymentData &&
@@ -96,7 +98,7 @@ export async function zkDeployContractFromUri(
9698
compilerMetadata.metadataUri,
9799
);
98100

99-
return proxy.address;
101+
deployedAddress = proxy.address;
100102
} else {
101103
throw new Error("Invalid deploy type");
102104
}
@@ -131,8 +133,24 @@ export async function zkDeployContractFromUri(
131133
compilerMetadata.metadataUri,
132134
);
133135

134-
return contract.address;
136+
deployedAddress = contract.address;
135137
}
138+
139+
// fire-and-forget verification, don't await
140+
try {
141+
zkVerify(
142+
deployedAddress,
143+
chainId,
144+
blockExplorerApiMap[chainId],
145+
"",
146+
storage,
147+
publishMetadataUri,
148+
);
149+
} catch (error) {
150+
// ignore error
151+
}
152+
153+
return deployedAddress;
136154
}
137155

138156
export async function getZkTransactionsForDeploy(): Promise<
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { ThirdwebStorage } from "@thirdweb-dev/storage";
2+
import { providers, utils } from "ethers";
3+
import invariant from "tiny-invariant";
4+
import { extractConstructorParamsFromAbi } from "../common/feature-detection/extractConstructorParamsFromAbi";
5+
import { getChainProvider } from "../constants/urls";
6+
import { Abi } from "../schema/contracts/custom";
7+
import { twProxyArtifactZK } from "./temp-artifact/TWProxy";
8+
import { fetchSourceFilesFromMetadata } from "../common/fetchSourceFilesFromMetadata";
9+
import { fetchRawPredeployMetadata } from "../common/feature-detection/fetchRawPredeployMetadata";
10+
import { fetchContractMetadata } from "../common/fetchContractMetadata";
11+
import { checkVerificationStatus } from "../common/verification";
12+
13+
const RequestStatus = {
14+
OK: "1",
15+
NOTOK: "0",
16+
};
17+
18+
//
19+
// ==================================
20+
// ======== Core Functions ==========
21+
// ==================================
22+
//
23+
24+
export async function zkVerify(
25+
contractAddress: string,
26+
chainId: number,
27+
explorerAPIUrl: string,
28+
explorerAPIKey: string,
29+
storage: ThirdwebStorage,
30+
contractUri?: string,
31+
encodedConstructorArgs?: string,
32+
) {
33+
try {
34+
const provider = getChainProvider(chainId, {});
35+
const contractBytecode = await provider.getCode(contractAddress);
36+
let compilerMetadata: any = {};
37+
if (contractBytecode === twProxyArtifactZK.bytecode) {
38+
compilerMetadata = {
39+
name: twProxyArtifactZK.contractName,
40+
abi: twProxyArtifactZK.abi,
41+
metadata: JSON.parse(twProxyArtifactZK.metadata.solc_metadata),
42+
zk_version: twProxyArtifactZK.metadata.zk_version,
43+
};
44+
compilerMetadata.metadata.sources = twProxyArtifactZK.sources;
45+
} else {
46+
invariant(contractUri, "No contract URI provided");
47+
const rawMeta = await fetchRawPredeployMetadata(contractUri, storage);
48+
const metadataUri = rawMeta.compilers?.zksolc?.metadataUri;
49+
50+
invariant(metadataUri, "ZkSolc metadata not found");
51+
const parsedMetadata = await fetchContractMetadata(metadataUri, storage);
52+
53+
compilerMetadata = {
54+
name: parsedMetadata.name,
55+
abi: parsedMetadata.abi,
56+
metadata: parsedMetadata.metadata,
57+
zk_version: rawMeta.zk_version,
58+
};
59+
}
60+
61+
const compilerVersion = compilerMetadata.metadata.compiler.version;
62+
const sources = await fetchSourceFilesFromMetadata(
63+
compilerMetadata,
64+
storage,
65+
);
66+
67+
const sourcesWithUrl = compilerMetadata.metadata.sources;
68+
const sourcesWithContents: Record<string, { content: string }> = {};
69+
for (const path of Object.keys(sourcesWithUrl)) {
70+
const sourceCode = sources.find((source) => path === source.filename);
71+
if (!sourceCode) {
72+
throw new Error(`Could not find source file for ${path}`);
73+
}
74+
sourcesWithContents[path] = {
75+
content: sourceCode.source,
76+
};
77+
}
78+
79+
const compilerInput: any = {
80+
language: "Solidity",
81+
settings: compilerMetadata.metadata.settings.optimizer,
82+
sources: sourcesWithContents,
83+
};
84+
85+
const compilationTarget =
86+
compilerMetadata.metadata.settings.compilationTarget;
87+
const targets = Object.keys(compilationTarget);
88+
const contractPath = targets[0];
89+
90+
const encodedArgs = encodedConstructorArgs
91+
? encodedConstructorArgs
92+
: await zkFetchConstructorParams(
93+
explorerAPIUrl,
94+
explorerAPIKey,
95+
contractAddress,
96+
compilerMetadata.abi,
97+
provider,
98+
);
99+
100+
const requestBody: Record<string, string> = {
101+
module: "contract",
102+
action: "verifysourcecode",
103+
contractaddress: contractAddress,
104+
sourceCode: compilerInput,
105+
codeformat: "solidity-standard-json-input",
106+
contractname: `${contractPath}:${compilerMetadata.name}`,
107+
compilerversion: `${compilerVersion.split("+")[0]}`,
108+
zkCompilerVersion: `v${compilerMetadata.zk_version}`,
109+
runs: compilerMetadata.metadata.settings.optimizer.runs,
110+
optimizationUsed: compilerMetadata.metadata.settings.optimizer.enabled
111+
? "1"
112+
: "0",
113+
constructorArguements: encodedArgs,
114+
};
115+
116+
const result = await fetch(`${explorerAPIUrl}`, {
117+
method: "POST",
118+
headers: { "Content-Type": "application/json" },
119+
body: JSON.stringify(requestBody),
120+
});
121+
122+
const data = await result.json();
123+
if (data.status === RequestStatus.OK) {
124+
console.info("Checking verification status...");
125+
const verificationStatus = await checkVerificationStatus(
126+
explorerAPIUrl,
127+
explorerAPIKey,
128+
data.result,
129+
);
130+
console.info(verificationStatus);
131+
} else {
132+
throw new Error(`${data.result}`);
133+
}
134+
} catch (e: any) {
135+
throw new Error(e.toString());
136+
}
137+
}
138+
139+
//
140+
// ==================================
141+
// ======== Helper Functions ========
142+
// ==================================
143+
//
144+
145+
async function zkFetchConstructorParams(
146+
explorerAPIUrl: string,
147+
explorerAPIKey: string,
148+
contractAddress: string,
149+
abi: Abi,
150+
provider: providers.Provider,
151+
): Promise<string> {
152+
const constructorParamTypes = extractConstructorParamsFromAbi(abi);
153+
if (constructorParamTypes.length === 0) {
154+
return "";
155+
}
156+
157+
const result = await fetch(
158+
`${explorerAPIUrl}?module=contract&action=getcontractcreation&contractaddresses=${contractAddress}&apikey=${explorerAPIKey}`,
159+
);
160+
const creationTx = await result.json();
161+
162+
if (
163+
creationTx &&
164+
creationTx.status === RequestStatus.OK &&
165+
creationTx.result[0] !== undefined
166+
) {
167+
const txHash = creationTx.result[0].txHash;
168+
const transaction = await provider.getTransaction(txHash);
169+
if (transaction.to === "0x0000000000000000000000000000000000008006") {
170+
const decoded = utils.defaultAbiCoder.decode(
171+
["bytes32", "bytes32", "bytes"],
172+
utils.hexDataSlice(transaction.data, 4),
173+
);
174+
175+
return decoded[2];
176+
} else {
177+
// TODO: decode for create2 deployments via factory
178+
return "";
179+
}
180+
} else {
181+
// Could not retrieve constructor parameters, using empty parameters as fallback
182+
return "";
183+
}
184+
}

0 commit comments

Comments
 (0)