anvil_zksync_core/
system_contracts.rs

1use std::path::{Path, PathBuf};
2
3use crate::deps::system_contracts::load_builtin_contract;
4use crate::node::ImpersonationManager;
5use anvil_zksync_config::types::{BoojumConfig, SystemContractsOptions};
6use zksync_contracts::{
7    read_sys_contract_bytecode, BaseSystemContracts, BaseSystemContractsHashes, ContractLanguage,
8    SystemContractCode, SystemContractsRepo,
9};
10use zksync_multivm::interface::TxExecutionMode;
11use zksync_types::bytecode::BytecodeHash;
12use zksync_types::{Address, ProtocolVersionId};
13
14/// Builder for SystemContracts
15#[derive(Debug, Default)]
16pub struct SystemContractsBuilder {
17    system_contracts_options: Option<SystemContractsOptions>,
18    system_contracts_path: Option<PathBuf>,
19    protocol_version: Option<ProtocolVersionId>,
20    use_evm_interpreter: bool,
21    boojum: BoojumConfig,
22}
23
24impl SystemContractsBuilder {
25    /// Create a new builder with default values
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    /// Set the system contracts options (e.g. Local, BuiltIn, BuiltInWithoutSecurity)
31    pub fn system_contracts_options(mut self, opts: SystemContractsOptions) -> Self {
32        self.system_contracts_options = Some(opts);
33        self
34    }
35
36    /// Set the system contracts path
37    pub fn system_contracts_path(mut self, path: Option<PathBuf>) -> Self {
38        self.system_contracts_path = path;
39        self
40    }
41
42    /// Set the protocol version
43    pub fn protocol_version(mut self, version: ProtocolVersionId) -> Self {
44        self.protocol_version = Some(version);
45        self
46    }
47
48    /// Enable or disable the EVM interpreter
49    pub fn with_evm_interpreter(mut self, flag: bool) -> Self {
50        self.use_evm_interpreter = flag;
51        self
52    }
53
54    /// Enable or disable Boojum
55    pub fn with_boojum(mut self, config: BoojumConfig) -> Self {
56        self.boojum = config;
57        self
58    }
59
60    /// Build the SystemContracts instance.
61    ///
62    /// This method will panic if the `system_contracts_options` is not provided.
63    /// For the protocol version, if none is provided, the latest version is used.
64    pub fn build(self) -> SystemContracts {
65        let options = self
66            .system_contracts_options
67            .expect("SystemContractsOptions must be provided");
68        let protocol_version = self
69            .protocol_version
70            .unwrap_or_else(ProtocolVersionId::latest);
71
72        tracing::debug!(
73            %protocol_version, use_evm_interpreter = self.use_evm_interpreter, use_boojum = self.boojum.use_boojum,
74            "Building SystemContracts"
75        );
76
77        SystemContracts::from_options(
78            options,
79            self.system_contracts_path,
80            protocol_version,
81            self.use_evm_interpreter,
82            self.boojum,
83        )
84    }
85}
86
87/// Holds the system contracts (and bootloader) that are used by the in-memory node.
88#[derive(Debug, Clone)]
89pub struct SystemContracts {
90    pub protocol_version: ProtocolVersionId,
91    baseline_contracts: BaseSystemContracts,
92    playground_contracts: BaseSystemContracts,
93    fee_estimate_contracts: BaseSystemContracts,
94    baseline_impersonating_contracts: BaseSystemContracts,
95    fee_estimate_impersonating_contracts: BaseSystemContracts,
96    use_evm_emulator: bool,
97    // For now, store the boojum switch flag here.
98    // Long term, we should probably refactor this code, and add another struct ('System')
99    // that would hold separate things for BoojumOS and for EraVM. (but that's too early for now).
100    pub boojum: BoojumConfig,
101}
102
103impl SystemContracts {
104    /// Creates a builder for SystemContracts
105    pub fn builder() -> SystemContractsBuilder {
106        SystemContractsBuilder::new()
107    }
108
109    /// Creates the SystemContracts that use the complied contracts from ZKSYNC_HOME path.
110    /// These are loaded at binary runtime.
111    pub fn from_options(
112        options: SystemContractsOptions,
113        system_contracts_path: Option<PathBuf>,
114        protocol_version: ProtocolVersionId,
115        use_evm_emulator: bool,
116        boojum: BoojumConfig,
117    ) -> Self {
118        tracing::info!(
119            %protocol_version,
120            use_evm_emulator,
121            boojum.use_boojum,
122            "initializing system contracts"
123        );
124        let path = system_contracts_path.unwrap_or_else(|| SystemContractsRepo::default().root);
125        Self {
126            protocol_version,
127            baseline_contracts: baseline_contracts(
128                options,
129                protocol_version,
130                use_evm_emulator,
131                &path,
132            ),
133            playground_contracts: playground(options, protocol_version, use_evm_emulator, &path),
134            fee_estimate_contracts: fee_estimate_contracts(
135                options,
136                protocol_version,
137                use_evm_emulator,
138                &path,
139            ),
140            baseline_impersonating_contracts: baseline_impersonating_contracts(
141                options,
142                protocol_version,
143                use_evm_emulator,
144                &path,
145            ),
146            fee_estimate_impersonating_contracts: fee_estimate_impersonating_contracts(
147                options,
148                protocol_version,
149                use_evm_emulator,
150                &path,
151            ),
152            use_evm_emulator,
153            boojum,
154        }
155    }
156
157    /// Whether it accepts the transactions that have 'null' as target.
158    /// This is used only when EVM emulator is enabled, or we're running in boojumos mode.
159    pub fn allow_no_target(&self) -> bool {
160        self.boojum.use_boojum || self.use_evm_emulator
161    }
162
163    pub fn contracts_for_l2_call(&self) -> &BaseSystemContracts {
164        self.contracts(TxExecutionMode::EthCall, false)
165    }
166
167    pub fn contracts_for_fee_estimate(&self, impersonating: bool) -> &BaseSystemContracts {
168        self.contracts(TxExecutionMode::EstimateFee, impersonating)
169    }
170
171    pub fn contracts(
172        &self,
173        execution_mode: TxExecutionMode,
174        impersonating: bool,
175    ) -> &BaseSystemContracts {
176        match (execution_mode, impersonating) {
177            // 'real' contracts, that do all the checks.
178            (TxExecutionMode::VerifyExecute, false) => &self.baseline_contracts,
179            // Ignore invalid signatures. These requests are often coming unsigned, and they keep changing the
180            // gas limit - so the signatures are often not matching.
181            (TxExecutionMode::EstimateFee, false) => &self.fee_estimate_contracts,
182            // Read-only call - don't check signatures, have a lower (fixed) gas limit.
183            (TxExecutionMode::EthCall, false) => &self.playground_contracts,
184            // Without account validation and sender related checks.
185            (TxExecutionMode::VerifyExecute, true) => &self.baseline_impersonating_contracts,
186            (TxExecutionMode::EstimateFee, true) => &self.fee_estimate_impersonating_contracts,
187            (TxExecutionMode::EthCall, true) => {
188                panic!("Account impersonating with eth_call is not supported")
189            }
190        }
191    }
192
193    pub fn base_system_contracts_hashes(&self) -> BaseSystemContractsHashes {
194        self.baseline_contracts.hashes()
195    }
196
197    pub fn system_contracts_for_initiator(
198        &self,
199        impersonation: &ImpersonationManager,
200        initiator: &Address,
201    ) -> BaseSystemContracts {
202        if impersonation.is_impersonating(initiator) {
203            tracing::info!("Executing tx from impersonated account {initiator:?}");
204            self.contracts(TxExecutionMode::VerifyExecute, true).clone()
205        } else {
206            self.contracts(TxExecutionMode::VerifyExecute, false)
207                .clone()
208        }
209    }
210}
211
212/// Creates BaseSystemContracts object with a specific bootloader.
213fn bsc_load_with_bootloader(
214    bootloader_bytecode: Vec<u8>,
215    options: SystemContractsOptions,
216    protocol_version: ProtocolVersionId,
217    use_evm_emulator: bool,
218    system_contracts_path: &Path,
219) -> BaseSystemContracts {
220    let repo = system_contracts_repo(system_contracts_path);
221    let hash = BytecodeHash::for_bytecode(&bootloader_bytecode);
222
223    let bootloader = SystemContractCode {
224        code: bootloader_bytecode,
225        hash: hash.value(),
226    };
227
228    let aa_bytecode = match options {
229        SystemContractsOptions::BuiltIn => {
230            load_builtin_contract(protocol_version, "DefaultAccount")
231        }
232        SystemContractsOptions::Local => {
233            repo.read_sys_contract_bytecode("", "DefaultAccount", None, ContractLanguage::Sol)
234        }
235        SystemContractsOptions::BuiltInWithoutSecurity => {
236            load_builtin_contract(protocol_version, "DefaultAccountNoSecurity")
237        }
238    };
239
240    let aa_hash = BytecodeHash::for_bytecode(&aa_bytecode);
241    let default_aa = SystemContractCode {
242        code: aa_bytecode,
243        hash: aa_hash.value(),
244    };
245
246    let evm_emulator = if use_evm_emulator {
247        let evm_emulator_bytecode = match options {
248            SystemContractsOptions::Local => {
249                read_sys_contract_bytecode("", "EvmEmulator", ContractLanguage::Yul)
250            }
251            SystemContractsOptions::BuiltIn | SystemContractsOptions::BuiltInWithoutSecurity => {
252                load_builtin_contract(protocol_version, "EvmEmulator")
253            }
254        };
255        let evm_emulator_hash = BytecodeHash::for_bytecode(&evm_emulator_bytecode);
256        Some(SystemContractCode {
257            code: evm_emulator_bytecode,
258            hash: evm_emulator_hash.value(),
259        })
260    } else {
261        None
262    };
263
264    BaseSystemContracts {
265        bootloader,
266        default_aa,
267        evm_emulator,
268    }
269}
270
271/// BaseSystemContracts with playground bootloader -  used for handling 'eth_calls'.
272fn playground(
273    options: SystemContractsOptions,
274    protocol_version: ProtocolVersionId,
275    use_evm_emulator: bool,
276    system_contracts_path: &Path,
277) -> BaseSystemContracts {
278    let repo = system_contracts_repo(system_contracts_path);
279    let bootloader_bytecode = match options {
280        SystemContractsOptions::BuiltIn | SystemContractsOptions::BuiltInWithoutSecurity => {
281            load_builtin_contract(protocol_version, "playground_batch")
282        }
283        SystemContractsOptions::Local => repo.read_sys_contract_bytecode(
284            "bootloader",
285            "playground_batch",
286            Some("Bootloader"),
287            ContractLanguage::Yul,
288        ),
289    };
290
291    bsc_load_with_bootloader(
292        bootloader_bytecode,
293        options,
294        protocol_version,
295        use_evm_emulator,
296        system_contracts_path,
297    )
298}
299
300/// Returns the system contracts for fee estimation.
301///
302/// # Returns
303///
304/// A `BaseSystemContracts` struct containing the system contracts used for handling 'eth_estimateGas'.
305/// It sets ENSURE_RETURNED_MAGIC to 0 and BOOTLOADER_TYPE to 'playground_block'
306fn fee_estimate_contracts(
307    options: SystemContractsOptions,
308    protocol_version: ProtocolVersionId,
309    use_evm_emulator: bool,
310    system_contracts_path: &Path,
311) -> BaseSystemContracts {
312    let repo = system_contracts_repo(system_contracts_path);
313    let bootloader_bytecode = match options {
314        SystemContractsOptions::BuiltIn | SystemContractsOptions::BuiltInWithoutSecurity => {
315            load_builtin_contract(protocol_version, "fee_estimate")
316        }
317        SystemContractsOptions::Local => repo.read_sys_contract_bytecode(
318            "bootloader",
319            "fee_estimate",
320            Some("Bootloader"),
321            ContractLanguage::Yul,
322        ),
323    };
324
325    bsc_load_with_bootloader(
326        bootloader_bytecode,
327        options,
328        protocol_version,
329        use_evm_emulator,
330        system_contracts_path,
331    )
332}
333
334fn fee_estimate_impersonating_contracts(
335    options: SystemContractsOptions,
336    protocol_version: ProtocolVersionId,
337    use_evm_emulator: bool,
338    system_contracts_path: &Path,
339) -> BaseSystemContracts {
340    let repo = system_contracts_repo(system_contracts_path);
341    let bootloader_bytecode = match options {
342        SystemContractsOptions::BuiltIn | SystemContractsOptions::BuiltInWithoutSecurity => {
343            load_builtin_contract(protocol_version, "fee_estimate_impersonating")
344        }
345        // Account impersonating is not supported with the local contracts
346        SystemContractsOptions::Local => repo.read_sys_contract_bytecode(
347            "bootloader",
348            "fee_estimate",
349            Some("Bootloader"),
350            ContractLanguage::Yul,
351        ),
352    };
353
354    bsc_load_with_bootloader(
355        bootloader_bytecode,
356        options,
357        protocol_version,
358        use_evm_emulator,
359        system_contracts_path,
360    )
361}
362
363fn baseline_contracts(
364    options: SystemContractsOptions,
365    protocol_version: ProtocolVersionId,
366    use_evm_emulator: bool,
367    system_contracts_path: &Path,
368) -> BaseSystemContracts {
369    let repo = system_contracts_repo(system_contracts_path);
370    let bootloader_bytecode = match options {
371        SystemContractsOptions::BuiltIn | SystemContractsOptions::BuiltInWithoutSecurity => {
372            load_builtin_contract(protocol_version, "proved_batch")
373        }
374        SystemContractsOptions::Local => repo.read_sys_contract_bytecode(
375            "bootloader",
376            "proved_batch",
377            Some("Bootloader"),
378            ContractLanguage::Yul,
379        ),
380    };
381    bsc_load_with_bootloader(
382        bootloader_bytecode,
383        options,
384        protocol_version,
385        use_evm_emulator,
386        system_contracts_path,
387    )
388}
389
390fn baseline_impersonating_contracts(
391    options: SystemContractsOptions,
392    protocol_version: ProtocolVersionId,
393    use_evm_emulator: bool,
394    system_contracts_path: &Path,
395) -> BaseSystemContracts {
396    let repo = system_contracts_repo(system_contracts_path);
397    let bootloader_bytecode = match options {
398        SystemContractsOptions::BuiltIn | SystemContractsOptions::BuiltInWithoutSecurity => {
399            load_builtin_contract(protocol_version, "proved_batch_impersonating")
400        }
401        // Account impersonating is not supported with the local contracts
402        SystemContractsOptions::Local => repo.read_sys_contract_bytecode(
403            "bootloader",
404            "proved_batch",
405            Some("Bootloader"),
406            ContractLanguage::Yul,
407        ),
408    };
409    bsc_load_with_bootloader(
410        bootloader_bytecode,
411        options,
412        protocol_version,
413        use_evm_emulator,
414        system_contracts_path,
415    )
416}
417
418fn system_contracts_repo(root: &Path) -> SystemContractsRepo {
419    SystemContractsRepo {
420        root: root.to_path_buf(),
421    }
422}