Skip to main content

zksync_dal/
contract_verification_dal.rs

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            // Serialization should always succeed.
92            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    /// Returns the next verification request for processing.
108    /// Considering the situation where processing of some request
109    /// can be interrupted (panic, pod restart, etc..),
110    /// `processing_timeout` parameter is added to avoid stuck requests.
111    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    /// Updates the verification request status and inserts the verification info upon successful verification.
170    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        // Serialization should always succeed.
196        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    /// Returns bytecode and calldata from the contract and the transaction that created it.
289    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                // `row.contract_address` and `row.data` are either both `None` or both `Some(_)`.
337                // In this arm it's checked that `row.contract_address` is `Some(_)`, so it's safe to unwrap `row.data`.
338                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    /// Returns true if the contract has a stored contracts_verification_info.
352    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}