1#![doc = include_str!("../doc/ContractVerificationDal.md")]
2use std::{
3 fmt::{Display, Formatter},
4 time::Duration,
5};
6
7use anyhow::Context as _;
8use sqlx::postgres::types::PgInterval;
9use zksync_db_connection::connection::Connection;
10use zksync_types::{
11 contract_verification_api::{
12 DeployContractCalldata, VerificationIncomingRequest, VerificationInfo, VerificationRequest,
13 VerificationRequestStatus,
14 },
15 event::DEPLOY_EVENT_SIGNATURE,
16 Address, CONTRACT_DEPLOYER_ADDRESS,
17};
18use zksync_utils::address_to_h256;
19
20use crate::{models::storage_verification_request::StorageVerificationRequest, Core};
21
22#[derive(Debug)]
23pub struct ContractVerificationDal<'a, 'c> {
24 pub(crate) storage: &'a mut Connection<'c, Core>,
25}
26
27#[derive(Debug)]
28enum Compiler {
29 ZkSolc,
30 Solc,
31 ZkVyper,
32 Vyper,
33}
34
35impl Display for Compiler {
36 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Self::ZkSolc => f.write_str("zksolc"),
39 Self::Solc => f.write_str("solc"),
40 Self::ZkVyper => f.write_str("zkvyper"),
41 Self::Vyper => f.write_str("vyper"),
42 }
43 }
44}
45
46impl ContractVerificationDal<'_, '_> {
47 pub async fn get_count_of_queued_verification_requests(&mut self) -> sqlx::Result<usize> {
48 sqlx::query!(
49 r#"
50 SELECT
51 COUNT(*) AS "count!"
52 FROM
53 contract_verification_requests
54 WHERE
55 status = 'queued'
56 "#
57 )
58 .fetch_one(self.storage.conn())
59 .await
60 .map(|row| row.count as usize)
61 }
62
63 pub async fn add_contract_verification_request(
64 &mut self,
65 query: VerificationIncomingRequest,
66 ) -> sqlx::Result<usize> {
67 sqlx::query!(
68 r#"
69 INSERT INTO
70 contract_verification_requests (
71 contract_address,
72 source_code,
73 contract_name,
74 zk_compiler_version,
75 compiler_version,
76 optimization_used,
77 optimizer_mode,
78 constructor_arguments,
79 is_system,
80 force_evmla,
81 status,
82 created_at,
83 updated_at
84 )
85 VALUES
86 ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'queued', NOW(), NOW())
87 RETURNING
88 id
89 "#,
90 query.contract_address.as_bytes(),
91 serde_json::to_string(&query.source_code_data).unwrap(),
93 query.contract_name,
94 query.compiler_versions.zk_compiler_version(),
95 query.compiler_versions.compiler_version(),
96 query.optimization_used,
97 query.optimizer_mode,
98 query.constructor_arguments.0,
99 query.is_system,
100 query.force_evmla,
101 )
102 .fetch_one(self.storage.conn())
103 .await
104 .map(|row| row.id as usize)
105 }
106
107 pub async fn get_next_queued_verification_request(
112 &mut self,
113 processing_timeout: Duration,
114 ) -> sqlx::Result<Option<VerificationRequest>> {
115 let processing_timeout = PgInterval {
116 months: 0,
117 days: 0,
118 microseconds: processing_timeout.as_micros() as i64,
119 };
120 let result = sqlx::query_as!(
121 StorageVerificationRequest,
122 r#"
123 UPDATE contract_verification_requests
124 SET
125 status = 'in_progress',
126 attempts = attempts + 1,
127 updated_at = NOW(),
128 processing_started_at = NOW()
129 WHERE
130 id = (
131 SELECT
132 id
133 FROM
134 contract_verification_requests
135 WHERE
136 status = 'queued'
137 OR (
138 status = 'in_progress'
139 AND processing_started_at < NOW() - $1::INTERVAL
140 )
141 ORDER BY
142 created_at
143 LIMIT
144 1
145 FOR UPDATE
146 SKIP LOCKED
147 )
148 RETURNING
149 id,
150 contract_address,
151 source_code,
152 contract_name,
153 zk_compiler_version,
154 compiler_version,
155 optimization_used,
156 optimizer_mode,
157 constructor_arguments,
158 is_system,
159 force_evmla
160 "#,
161 &processing_timeout
162 )
163 .fetch_optional(self.storage.conn())
164 .await?
165 .map(Into::into);
166 Ok(result)
167 }
168
169 pub async fn save_verification_info(
171 &mut self,
172 verification_info: VerificationInfo,
173 ) -> anyhow::Result<()> {
174 let mut transaction = self
175 .storage
176 .start_transaction()
177 .await
178 .context("start_transaction()")?;
179
180 sqlx::query!(
181 r#"
182 UPDATE contract_verification_requests
183 SET
184 status = 'successful',
185 updated_at = NOW()
186 WHERE
187 id = $1
188 "#,
189 verification_info.request.id as i64,
190 )
191 .execute(transaction.conn())
192 .await?;
193
194 let address = verification_info.request.req.contract_address;
195 let verification_info_json = serde_json::to_value(verification_info)
197 .expect("Failed to serialize verification info into serde_json");
198 sqlx::query!(
199 r#"
200 INSERT INTO
201 contracts_verification_info (address, verification_info)
202 VALUES
203 ($1, $2)
204 ON CONFLICT (address) DO
205 UPDATE
206 SET
207 verification_info = $2
208 "#,
209 address.as_bytes(),
210 &verification_info_json
211 )
212 .execute(transaction.conn())
213 .await?;
214
215 transaction.commit().await.context("commit()")?;
216 Ok(())
217 }
218
219 pub async fn save_verification_error(
220 &mut self,
221 id: usize,
222 error: String,
223 compilation_errors: serde_json::Value,
224 panic_message: Option<String>,
225 ) -> sqlx::Result<()> {
226 sqlx::query!(
227 r#"
228 UPDATE contract_verification_requests
229 SET
230 status = 'failed',
231 updated_at = NOW(),
232 error = $2,
233 compilation_errors = $3,
234 panic_message = $4
235 WHERE
236 id = $1
237 "#,
238 id as i64,
239 error.as_str(),
240 &compilation_errors,
241 panic_message
242 )
243 .execute(self.storage.conn())
244 .await?;
245 Ok(())
246 }
247
248 pub async fn get_verification_request_status(
249 &mut self,
250 id: usize,
251 ) -> anyhow::Result<Option<VerificationRequestStatus>> {
252 let Some(row) = sqlx::query!(
253 r#"
254 SELECT
255 status,
256 error,
257 compilation_errors
258 FROM
259 contract_verification_requests
260 WHERE
261 id = $1
262 "#,
263 id as i64,
264 )
265 .fetch_optional(self.storage.conn())
266 .await?
267 else {
268 return Ok(None);
269 };
270
271 let mut compilation_errors = vec![];
272 if let Some(errors) = row.compilation_errors {
273 for value in errors.as_array().context("expected an array")? {
274 compilation_errors.push(value.as_str().context("expected string")?.to_string());
275 }
276 }
277 Ok(Some(VerificationRequestStatus {
278 status: row.status,
279 error: row.error,
280 compilation_errors: if compilation_errors.is_empty() {
281 None
282 } else {
283 Some(compilation_errors)
284 },
285 }))
286 }
287
288 pub async fn get_contract_info_for_verification(
290 &mut self,
291 address: Address,
292 ) -> anyhow::Result<Option<(Vec<u8>, DeployContractCalldata)>> {
293 let address_h256 = address_to_h256(&address);
294 let Some(row) = sqlx::query!(
295 r#"
296 SELECT
297 factory_deps.bytecode,
298 transactions.data AS "data?",
299 transactions.contract_address AS "contract_address?"
300 FROM
301 (
302 SELECT
303 miniblock_number,
304 tx_hash,
305 topic3
306 FROM
307 events
308 WHERE
309 address = $1
310 AND topic1 = $2
311 AND topic4 = $3
312 LIMIT
313 1
314 ) deploy_event
315 JOIN factory_deps ON factory_deps.bytecode_hash = deploy_event.topic3
316 LEFT JOIN transactions ON transactions.hash = deploy_event.tx_hash
317 WHERE
318 deploy_event.miniblock_number <= (
319 SELECT
320 MAX(number)
321 FROM
322 miniblocks
323 )
324 "#,
325 CONTRACT_DEPLOYER_ADDRESS.as_bytes(),
326 DEPLOY_EVENT_SIGNATURE.as_bytes(),
327 address_h256.as_bytes(),
328 )
329 .fetch_optional(self.storage.conn())
330 .await?
331 else {
332 return Ok(None);
333 };
334 let calldata = match row.contract_address {
335 Some(contract_address) if contract_address == CONTRACT_DEPLOYER_ADDRESS.0.to_vec() => {
336 let data: serde_json::Value = row.data.context("data missing")?;
339 let calldata_str: String = serde_json::from_value(
340 data.get("calldata").context("calldata missing")?.clone(),
341 )
342 .context("failed parsing calldata")?;
343 let calldata = hex::decode(&calldata_str[2..]).context("invalid calldata")?;
344 DeployContractCalldata::Deploy(calldata)
345 }
346 _ => DeployContractCalldata::Ignore,
347 };
348 Ok(Some((row.bytecode, calldata)))
349 }
350
351 pub async fn is_contract_verified(&mut self, address: Address) -> sqlx::Result<bool> {
353 let count = sqlx::query!(
354 r#"
355 SELECT
356 COUNT(*) AS "count!"
357 FROM
358 contracts_verification_info
359 WHERE
360 address = $1
361 "#,
362 address.as_bytes()
363 )
364 .fetch_one(self.storage.conn())
365 .await?
366 .count;
367 Ok(count > 0)
368 }
369
370 async fn get_compiler_versions(&mut self, compiler: Compiler) -> sqlx::Result<Vec<String>> {
371 let compiler = format!("{compiler}");
372 let versions: Vec<_> = sqlx::query!(
373 r#"
374 SELECT
375 VERSION
376 FROM
377 compiler_versions
378 WHERE
379 compiler = $1
380 ORDER BY
381 VERSION
382 "#,
383 &compiler
384 )
385 .fetch_all(self.storage.conn())
386 .await?
387 .into_iter()
388 .map(|row| row.version)
389 .collect();
390 Ok(versions)
391 }
392
393 pub async fn get_zksolc_versions(&mut self) -> sqlx::Result<Vec<String>> {
394 self.get_compiler_versions(Compiler::ZkSolc).await
395 }
396
397 pub async fn get_solc_versions(&mut self) -> sqlx::Result<Vec<String>> {
398 self.get_compiler_versions(Compiler::Solc).await
399 }
400
401 pub async fn get_zkvyper_versions(&mut self) -> sqlx::Result<Vec<String>> {
402 self.get_compiler_versions(Compiler::ZkVyper).await
403 }
404
405 pub async fn get_vyper_versions(&mut self) -> sqlx::Result<Vec<String>> {
406 self.get_compiler_versions(Compiler::Vyper).await
407 }
408
409 async fn set_compiler_versions(
410 &mut self,
411 compiler: Compiler,
412 versions: Vec<String>,
413 ) -> anyhow::Result<()> {
414 let mut transaction = self
415 .storage
416 .start_transaction()
417 .await
418 .context("start_transaction")?;
419 let compiler = format!("{compiler}");
420
421 sqlx::query!(
422 r#"
423 DELETE FROM compiler_versions
424 WHERE
425 compiler = $1
426 "#,
427 &compiler
428 )
429 .execute(transaction.conn())
430 .await?;
431
432 sqlx::query!(
433 r#"
434 INSERT INTO
435 compiler_versions (VERSION, compiler, created_at, updated_at)
436 SELECT
437 u.version,
438 $2,
439 NOW(),
440 NOW()
441 FROM
442 UNNEST($1::TEXT[]) AS u (VERSION)
443 ON CONFLICT (VERSION, compiler) DO NOTHING
444 "#,
445 &versions,
446 &compiler,
447 )
448 .execute(transaction.conn())
449 .await?;
450
451 transaction.commit().await.context("commit()")?;
452 Ok(())
453 }
454
455 pub async fn set_zksolc_versions(&mut self, versions: Vec<String>) -> anyhow::Result<()> {
456 self.set_compiler_versions(Compiler::ZkSolc, versions).await
457 }
458
459 pub async fn set_solc_versions(&mut self, versions: Vec<String>) -> anyhow::Result<()> {
460 self.set_compiler_versions(Compiler::Solc, versions).await
461 }
462
463 pub async fn set_zkvyper_versions(&mut self, versions: Vec<String>) -> anyhow::Result<()> {
464 self.set_compiler_versions(Compiler::ZkVyper, versions)
465 .await
466 }
467
468 pub async fn set_vyper_versions(&mut self, versions: Vec<String>) -> anyhow::Result<()> {
469 self.set_compiler_versions(Compiler::Vyper, versions).await
470 }
471
472 pub async fn get_all_successful_requests(&mut self) -> sqlx::Result<Vec<VerificationRequest>> {
473 let result = sqlx::query_as!(
474 StorageVerificationRequest,
475 r#"
476 SELECT
477 id,
478 contract_address,
479 source_code,
480 contract_name,
481 zk_compiler_version,
482 compiler_version,
483 optimization_used,
484 optimizer_mode,
485 constructor_arguments,
486 is_system,
487 force_evmla
488 FROM
489 contract_verification_requests
490 WHERE
491 status = 'successful'
492 ORDER BY
493 id
494 "#,
495 )
496 .fetch_all(self.storage.conn())
497 .await?
498 .into_iter()
499 .map(Into::into)
500 .collect();
501 Ok(result)
502 }
503
504 pub async fn get_contract_verification_info(
505 &mut self,
506 address: Address,
507 ) -> anyhow::Result<Option<VerificationInfo>> {
508 let Some(row) = sqlx::query!(
509 r#"
510 SELECT
511 verification_info
512 FROM
513 contracts_verification_info
514 WHERE
515 address = $1
516 "#,
517 address.as_bytes(),
518 )
519 .fetch_optional(self.storage.conn())
520 .await?
521 else {
522 return Ok(None);
523 };
524 let Some(info) = row.verification_info else {
525 return Ok(None);
526 };
527 Ok(Some(serde_json::from_value(info).context("invalid info")?))
528 }
529}