foundry_common/
contracts.rs

1//! Commonly used contract types and functions.
2
3use crate::{compile::PathOrContractInfo, find_metadata_start, strip_bytecode_placeholders};
4use alloy_dyn_abi::JsonAbiExt;
5use alloy_json_abi::{Event, Function, JsonAbi};
6use alloy_primitives::{Address, B256, Bytes, Selector, hex};
7use eyre::{OptionExt, Result};
8use foundry_compilers::{
9    ArtifactId, Project, ProjectCompileOutput,
10    artifacts::{
11        BytecodeObject, CompactBytecode, CompactContractBytecode, CompactContractBytecodeCow,
12        CompactDeployedBytecode, ConfigurableContractArtifact, ContractBytecodeSome, Offsets,
13        StorageLayout,
14    },
15    utils::canonicalized,
16};
17use std::{
18    collections::BTreeMap,
19    ops::Deref,
20    path::{Path, PathBuf},
21    str::FromStr,
22    sync::Arc,
23};
24
25/// Libraries' runtime code always starts with the following instruction:
26/// `PUSH20 0x0000000000000000000000000000000000000000`
27///
28/// See: <https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries>
29const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] =
30    hex!("730000000000000000000000000000000000000000");
31
32/// Subset of [CompactBytecode] excluding sourcemaps.
33#[expect(missing_docs)]
34#[derive(Debug, Clone)]
35pub struct BytecodeData {
36    pub object: Option<BytecodeObject>,
37    pub link_references: BTreeMap<String, BTreeMap<String, Vec<Offsets>>>,
38    pub immutable_references: BTreeMap<String, Vec<Offsets>>,
39}
40
41impl BytecodeData {
42    fn bytes(&self) -> Option<&Bytes> {
43        self.object.as_ref().and_then(|b| b.as_bytes())
44    }
45}
46
47impl From<CompactBytecode> for BytecodeData {
48    fn from(bytecode: CompactBytecode) -> Self {
49        Self {
50            object: Some(bytecode.object),
51            link_references: bytecode.link_references,
52            immutable_references: BTreeMap::new(),
53        }
54    }
55}
56
57impl From<CompactDeployedBytecode> for BytecodeData {
58    fn from(bytecode: CompactDeployedBytecode) -> Self {
59        let (object, link_references) = if let Some(compact) = bytecode.bytecode {
60            (Some(compact.object), compact.link_references)
61        } else {
62            (None, BTreeMap::new())
63        };
64        Self { object, link_references, immutable_references: bytecode.immutable_references }
65    }
66}
67
68/// Container for commonly used contract data.
69#[derive(Debug)]
70pub struct ContractData {
71    /// Contract name.
72    pub name: String,
73    /// Contract ABI.
74    pub abi: JsonAbi,
75    /// Contract creation code.
76    pub bytecode: Option<BytecodeData>,
77    /// Contract runtime code.
78    pub deployed_bytecode: Option<BytecodeData>,
79    /// Contract storage layout, if available.
80    pub storage_layout: Option<Arc<StorageLayout>>,
81}
82
83impl ContractData {
84    /// Returns reference to bytes of contract creation code, if present.
85    pub fn bytecode(&self) -> Option<&Bytes> {
86        self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
87    }
88
89    /// Returns reference to bytes of contract deployed code, if present.
90    pub fn deployed_bytecode(&self) -> Option<&Bytes> {
91        self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty())
92    }
93
94    /// Returns the bytecode without placeholders, if present.
95    pub fn bytecode_without_placeholders(&self) -> Option<Bytes> {
96        strip_bytecode_placeholders(self.bytecode.as_ref()?.object.as_ref()?)
97    }
98
99    /// Returns the deployed bytecode without placeholders, if present.
100    pub fn deployed_bytecode_without_placeholders(&self) -> Option<Bytes> {
101        strip_bytecode_placeholders(self.deployed_bytecode.as_ref()?.object.as_ref()?)
102    }
103}
104
105/// Builder for creating a `ContractsByArtifact` instance, optionally including storage layouts
106/// from project compile output.
107pub struct ContractsByArtifactBuilder<'a> {
108    /// All compiled artifact bytecodes (borrowed).
109    artifacts: BTreeMap<ArtifactId, CompactContractBytecodeCow<'a>>,
110    /// Optionally collected storage layouts for matching artifact IDs.
111    storage_layouts: BTreeMap<ArtifactId, StorageLayout>,
112}
113
114impl<'a> ContractsByArtifactBuilder<'a> {
115    /// Creates a new builder from artifacts with present bytecode iterator.
116    pub fn new(
117        artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecodeCow<'a>)>,
118    ) -> Self {
119        Self { artifacts: artifacts.into_iter().collect(), storage_layouts: BTreeMap::new() }
120    }
121
122    /// Add storage layouts from the given `ProjectCompileOutput` to known artifacts.
123    pub fn with_output(self, output: &ProjectCompileOutput, base: &Path) -> Self {
124        self.with_storage_layouts(output.artifact_ids().filter_map(|(id, artifact)| {
125            artifact
126                .storage_layout
127                .as_ref()
128                .map(|layout| (id.with_stripped_file_prefixes(base), layout.clone()))
129        }))
130    }
131
132    /// Add storage layouts.
133    pub fn with_storage_layouts(
134        mut self,
135        layouts: impl IntoIterator<Item = (ArtifactId, StorageLayout)>,
136    ) -> Self {
137        self.storage_layouts.extend(layouts);
138        self
139    }
140
141    /// Builds `ContractsByArtifact`.
142    pub fn build(self) -> ContractsByArtifact {
143        let map = self
144            .artifacts
145            .into_iter()
146            .filter_map(|(id, artifact)| {
147                let name = id.name.clone();
148                let CompactContractBytecodeCow { abi, bytecode, deployed_bytecode } = artifact;
149
150                Some((
151                    id.clone(),
152                    ContractData {
153                        name,
154                        abi: abi?.into_owned(),
155                        bytecode: bytecode.map(|b| b.into_owned().into()),
156                        deployed_bytecode: deployed_bytecode.map(|b| b.into_owned().into()),
157                        storage_layout: self.storage_layouts.get(&id).map(|l| Arc::new(l.clone())),
158                    },
159                ))
160            })
161            .collect();
162
163        ContractsByArtifact(Arc::new(map))
164    }
165}
166
167type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData);
168
169/// Wrapper type that maps an artifact to a contract ABI and bytecode.
170#[derive(Clone, Default, Debug)]
171pub struct ContractsByArtifact(Arc<BTreeMap<ArtifactId, ContractData>>);
172
173impl ContractsByArtifact {
174    /// Creates a new instance by collecting all artifacts with present bytecode from an iterator.
175    pub fn new(artifacts: impl IntoIterator<Item = (ArtifactId, CompactContractBytecode)>) -> Self {
176        let map = artifacts
177            .into_iter()
178            .filter_map(|(id, artifact)| {
179                let name = id.name.clone();
180                let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact;
181                Some((
182                    id,
183                    ContractData {
184                        name,
185                        abi: abi?,
186                        bytecode: bytecode.map(Into::into),
187                        deployed_bytecode: deployed_bytecode.map(Into::into),
188                        storage_layout: None,
189                    },
190                ))
191            })
192            .collect();
193        Self(Arc::new(map))
194    }
195
196    /// Clears all contracts.
197    pub fn clear(&mut self) {
198        *self = Self::default();
199    }
200
201    /// Finds a contract which has a similar bytecode as `code`.
202    pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
203        self.find_by_code(code, 0.1, true, ContractData::bytecode)
204    }
205
206    /// Finds a contract which has a similar deployed bytecode as `code`.
207    pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
208        self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
209    }
210
211    /// Finds a contract based on provided bytecode and accepted match score.
212    /// If strip constructor args flag is true then removes args from bytecode to compare.
213    fn find_by_code(
214        &self,
215        code: &[u8],
216        accepted_score: f64,
217        strip_ctor_args: bool,
218        get: impl Fn(&ContractData) -> Option<&Bytes>,
219    ) -> Option<ArtifactWithContractRef<'_>> {
220        self.iter()
221            .filter_map(|(id, contract)| {
222                if let Some(deployed_bytecode) = get(contract) {
223                    let mut code = code;
224                    if strip_ctor_args && code.len() > deployed_bytecode.len() {
225                        // Try to decode ctor args with contract abi.
226                        if let Some(constructor) = contract.abi.constructor() {
227                            let constructor_args = &code[deployed_bytecode.len()..];
228                            if constructor.abi_decode_input(constructor_args).is_ok() {
229                                // If we can decode args with current abi then remove args from
230                                // code to compare.
231                                code = &code[..deployed_bytecode.len()]
232                            }
233                        }
234                    };
235
236                    let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
237                    (score <= accepted_score).then_some((score, (id, contract)))
238                } else {
239                    None
240                }
241            })
242            .min_by(|(score1, _), (score2, _)| score1.total_cmp(score2))
243            .map(|(_, data)| data)
244    }
245
246    /// Finds a contract which deployed bytecode exactly matches the given code. Accounts for link
247    /// references and immutables.
248    pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
249        // Immediately return None if the code is empty.
250        if code.is_empty() {
251            return None;
252        }
253
254        let mut partial_match = None;
255        self.iter()
256            .find(|(id, contract)| {
257                let Some(deployed_bytecode) = &contract.deployed_bytecode else {
258                    return false;
259                };
260                let Some(deployed_code) = &deployed_bytecode.object else {
261                    return false;
262                };
263
264                let len = match deployed_code {
265                    BytecodeObject::Bytecode(bytes) => bytes.len(),
266                    BytecodeObject::Unlinked(bytes) => bytes.len() / 2,
267                };
268
269                if len != code.len() {
270                    return false;
271                }
272
273                // Collect ignored offsets by chaining link and immutable references.
274                let mut ignored = deployed_bytecode
275                    .immutable_references
276                    .values()
277                    .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values()))
278                    .flatten()
279                    .cloned()
280                    .collect::<Vec<_>>();
281
282                // For libraries solidity adds a call protection prefix to the bytecode. We need to
283                // ignore it as it includes library address determined at runtime.
284                // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and
285                // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172
286                let has_call_protection = match deployed_code {
287                    BytecodeObject::Bytecode(bytes) => {
288                        bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
289                    }
290                    BytecodeObject::Unlinked(bytes) => {
291                        if let Ok(bytes) =
292                            Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2])
293                        {
294                            bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX)
295                        } else {
296                            false
297                        }
298                    }
299                };
300
301                if has_call_protection {
302                    ignored.push(Offsets { start: 1, length: 20 });
303                }
304
305                let metadata_start = find_metadata_start(code);
306
307                if let Some(metadata) = metadata_start {
308                    ignored.push(Offsets {
309                        start: metadata as u32,
310                        length: (code.len() - metadata) as u32,
311                    });
312                }
313
314                ignored.sort_by_key(|o| o.start);
315
316                let mut left = 0;
317                for offset in ignored {
318                    let right = offset.start as usize;
319
320                    let matched = match deployed_code {
321                        BytecodeObject::Bytecode(bytes) => bytes[left..right] == code[left..right],
322                        BytecodeObject::Unlinked(bytes) => {
323                            if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) {
324                                bytes == code[left..right]
325                            } else {
326                                false
327                            }
328                        }
329                    };
330
331                    if !matched {
332                        return false;
333                    }
334
335                    left = right + offset.length as usize;
336                }
337
338                let is_partial = if left < code.len() {
339                    match deployed_code {
340                        BytecodeObject::Bytecode(bytes) => bytes[left..] == code[left..],
341                        BytecodeObject::Unlinked(bytes) => {
342                            if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) {
343                                bytes == code[left..]
344                            } else {
345                                false
346                            }
347                        }
348                    }
349                } else {
350                    true
351                };
352
353                if !is_partial {
354                    return false;
355                }
356
357                let Some(metadata) = metadata_start else { return true };
358
359                let exact_match = match deployed_code {
360                    BytecodeObject::Bytecode(bytes) => bytes[metadata..] == code[metadata..],
361                    BytecodeObject::Unlinked(bytes) => {
362                        if let Ok(bytes) = Bytes::from_str(&bytes[metadata * 2..]) {
363                            bytes == code[metadata..]
364                        } else {
365                            false
366                        }
367                    }
368                };
369
370                if exact_match {
371                    true
372                } else {
373                    partial_match = Some((*id, *contract));
374                    false
375                }
376            })
377            .or(partial_match)
378    }
379
380    /// Finds a contract which has the same contract name or identifier as `id`. If more than one is
381    /// found, return error.
382    pub fn find_by_name_or_identifier(
383        &self,
384        id: &str,
385    ) -> Result<Option<ArtifactWithContractRef<'_>>> {
386        let contracts = self
387            .iter()
388            .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id)
389            .collect::<Vec<_>>();
390
391        if contracts.len() > 1 {
392            eyre::bail!("{id} has more than one implementation.");
393        }
394
395        Ok(contracts.first().copied())
396    }
397
398    /// Finds abi for contract which has the same contract name or identifier as `id`.
399    pub fn find_abi_by_name_or_identifier(&self, id: &str) -> Option<JsonAbi> {
400        self.iter()
401            .find(|(artifact, _)| {
402                artifact.name.split(".").next().unwrap() == id || artifact.identifier() == id
403            })
404            .map(|(_, contract)| contract.abi.clone())
405    }
406
407    /// Finds abi by name or source path
408    ///
409    /// Returns the abi and the contract name.
410    pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> {
411        self.iter()
412            .find(|(artifact, _)| {
413                artifact.name == name_or_path || artifact.source == Path::new(name_or_path)
414            })
415            .map(|(_, contract)| (contract.abi.clone(), contract.name.clone()))
416    }
417
418    /// Flattens the contracts into functions, events and errors.
419    pub fn flatten(&self) -> (BTreeMap<Selector, Function>, BTreeMap<B256, Event>, JsonAbi) {
420        let mut funcs = BTreeMap::new();
421        let mut events = BTreeMap::new();
422        let mut errors_abi = JsonAbi::new();
423        for (_name, contract) in self.iter() {
424            for func in contract.abi.functions() {
425                funcs.insert(func.selector(), func.clone());
426            }
427            for event in contract.abi.events() {
428                events.insert(event.selector(), event.clone());
429            }
430            for error in contract.abi.errors() {
431                errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone());
432            }
433        }
434        (funcs, events, errors_abi)
435    }
436}
437
438impl From<ProjectCompileOutput> for ContractsByArtifact {
439    fn from(value: ProjectCompileOutput) -> Self {
440        Self::new(value.into_artifacts().map(|(id, ar)| {
441            (
442                id,
443                CompactContractBytecode {
444                    abi: ar.abi,
445                    bytecode: ar.bytecode,
446                    deployed_bytecode: ar.deployed_bytecode,
447                },
448            )
449        }))
450    }
451}
452
453impl Deref for ContractsByArtifact {
454    type Target = BTreeMap<ArtifactId, ContractData>;
455
456    fn deref(&self) -> &Self::Target {
457        &self.0
458    }
459}
460
461/// Wrapper type that maps an address to a contract identifier and contract ABI.
462pub type ContractsByAddress = BTreeMap<Address, (String, JsonAbi)>;
463
464/// Very simple fuzzy matching of contract bytecode.
465///
466/// Returns a value between `0.0` (identical) and `1.0` (completely different).
467pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 {
468    // Make sure `a` is the longer one.
469    if a.len() < b.len() {
470        std::mem::swap(&mut a, &mut b);
471    }
472
473    // Account for different lengths.
474    let mut n_different_bytes = a.len() - b.len();
475
476    // If the difference is more than 32 bytes and more than 10% of the total length,
477    // we assume the bytecodes are completely different.
478    // This is a simple heuristic to avoid checking every byte when the lengths are very different.
479    // 32 is chosen to be a reasonable minimum as it's the size of metadata hashes and one EVM word.
480    if n_different_bytes > 32 && n_different_bytes * 10 > a.len() {
481        return 1.0;
482    }
483
484    // Count different bytes.
485    // SAFETY: `a` is longer than `b`.
486    n_different_bytes += unsafe { count_different_bytes(a, b) };
487
488    n_different_bytes as f64 / a.len() as f64
489}
490
491/// Returns the amount of different bytes between two slices.
492///
493/// # Safety
494///
495/// `a` must be at least as long as `b`.
496unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize {
497    // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`,
498    // however this function is very hot, and has been written to be as primitive as
499    // possible for lower optimization levels.
500
501    let a_ptr = a.as_ptr();
502    let b_ptr = b.as_ptr();
503    let len = b.len();
504
505    let mut sum = 0;
506    let mut i = 0;
507    while i < len {
508        // SAFETY: `a` is at least as long as `b`, and `i` is in bound of `b`.
509        sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize;
510        i += 1;
511    }
512    sum
513}
514
515/// Returns contract name for a given contract identifier.
516///
517/// Artifact/Contract identifier can take the following form:
518/// `<artifact file name>:<contract name>`, the `artifact file name` is the name of the json file of
519/// the contract's artifact and the contract name is the name of the solidity contract, like
520/// `SafeTransferLibTest.json:SafeTransferLibTest`
521///
522/// This returns the `contract name` part
523///
524/// # Example
525///
526/// ```
527/// use foundry_common::*;
528/// assert_eq!(
529///     "SafeTransferLibTest",
530///     get_contract_name("SafeTransferLibTest.json:SafeTransferLibTest")
531/// );
532/// ```
533pub fn get_contract_name(id: &str) -> &str {
534    id.rsplit(':').next().unwrap_or(id)
535}
536
537/// This returns the `file name` part, See [`get_contract_name`]
538///
539/// # Example
540///
541/// ```
542/// use foundry_common::*;
543/// assert_eq!(
544///     "SafeTransferLibTest.json",
545///     get_file_name("SafeTransferLibTest.json:SafeTransferLibTest")
546/// );
547/// ```
548pub fn get_file_name(id: &str) -> &str {
549    id.split(':').next().unwrap_or(id)
550}
551
552/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome
553pub fn compact_to_contract(contract: CompactContractBytecode) -> Result<ContractBytecodeSome> {
554    Ok(ContractBytecodeSome {
555        abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?,
556        bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(),
557        deployed_bytecode: contract
558            .deployed_bytecode
559            .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))?
560            .into(),
561    })
562}
563
564/// Returns the canonicalized target path for the given identifier.
565pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result<PathBuf> {
566    match identifier {
567        PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))),
568        PathOrContractInfo::ContractInfo(info) => {
569            if let Some(path) = info.path.as_ref() {
570                let path = canonicalized(project.root().join(path));
571                let sources = project.sources()?;
572                let contract_path = sources
573                    .iter()
574                    .find_map(|(src_path, _)| {
575                        if **src_path == path {
576                            return Some(src_path.clone());
577                        }
578                        None
579                    })
580                    .ok_or_else(|| {
581                        eyre::eyre!(
582                            "Could not find source file for contract `{}` at {}",
583                            info.name,
584                            path.strip_prefix(project.root()).unwrap().display()
585                        )
586                    })?;
587                return Ok(contract_path);
588            }
589            // If ContractInfo.path hasn't been provided we try to find the contract using the name.
590            // This will fail if projects have multiple contracts with the same name. In that case,
591            // path must be specified.
592            let path = project.find_contract_path(&info.name)?;
593            Ok(path)
594        }
595    }
596}
597
598/// Returns the target artifact given the path and name.
599pub fn find_matching_contract_artifact(
600    output: &mut ProjectCompileOutput,
601    target_path: &Path,
602    target_name: Option<&str>,
603) -> eyre::Result<ConfigurableContractArtifact> {
604    if let Some(name) = target_name {
605        output
606            .remove(target_path, name)
607            .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts"))
608    } else {
609        let possible_targets = output
610            .artifact_ids()
611            .filter(|(id, _artifact)| id.source == target_path)
612            .collect::<Vec<_>>();
613
614        if possible_targets.is_empty() {
615            eyre::bail!(
616                "Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"
617            );
618        }
619
620        let (target_id, target_artifact) = possible_targets[0].clone();
621        if possible_targets.len() == 1 {
622            return Ok(target_artifact.clone());
623        }
624
625        // If all artifact_ids in `possible_targets` have the same name (without ".", indicates
626        // additional compiler profiles), it means that there are multiple contracts in the
627        // same file.
628        if !target_id.name.contains(".")
629            && possible_targets.iter().any(|(id, _)| id.name != target_id.name)
630        {
631            eyre::bail!(
632                "Multiple contracts found in the same file, please specify the target <path>:<contract> or <contract>"
633            );
634        }
635
636        // Otherwise, we're dealing with additional compiler profiles wherein `id.source` is the
637        // same but `id.path` is different.
638        let artifact = possible_targets
639            .iter()
640            .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None })
641            .unwrap_or(target_artifact);
642
643        Ok(artifact.clone())
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650
651    #[test]
652    fn bytecode_diffing() {
653        assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0);
654        assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0);
655
656        let a_100 = &b"a".repeat(100)[..];
657        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0);
658        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0);
659        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0);
660        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0);
661        assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0);
662
663        let a_99 = &b"a".repeat(99)[..];
664        assert!(bytecode_diff_score(a_100, a_99) <= 0.01);
665    }
666
667    #[test]
668    fn find_by_deployed_code_exact_with_empty_deployed() {
669        let contracts = ContractsByArtifact::new(vec![]);
670
671        assert!(contracts.find_by_deployed_code_exact(&[]).is_none());
672    }
673}