1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
4#![allow(ambiguous_glob_reexports)]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6
7#[macro_use]
8extern crate tracing;
9
10use semver::Version;
11use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
12use std::{
13 collections::{BTreeMap, HashSet},
14 fmt,
15 path::{Path, PathBuf},
16 str::FromStr,
17};
18
19pub mod error;
20pub use error::*;
21pub mod ast;
22pub use ast::*;
23pub mod remappings;
24pub use remappings::*;
25pub mod bytecode;
26pub use bytecode::*;
27pub mod contract;
28pub use contract::*;
29pub mod configurable;
30pub mod hh;
31pub use configurable::*;
32pub mod output_selection;
33pub mod serde_helpers;
34pub mod sourcemap;
35pub mod sources;
36use crate::output_selection::{ContractOutputSelection, OutputSelection};
37use foundry_compilers_core::{
38 error::SolcError,
39 utils::{
40 BERLIN_SOLC, BYZANTIUM_SOLC, CANCUN_SOLC, CONSTANTINOPLE_SOLC, ISTANBUL_SOLC, LONDON_SOLC,
41 OSAKA_SOLC, PARIS_SOLC, PETERSBURG_SOLC, PRAGUE_SOLC, SHANGHAI_SOLC, strip_prefix_owned,
42 },
43};
44pub use serde_helpers::{deserialize_bytes, deserialize_opt_bytes};
45pub use sources::*;
46
47pub type FileToContractsMap<T> = BTreeMap<PathBuf, BTreeMap<String, T>>;
53
54pub type Contracts = FileToContractsMap<Contract>;
56
57pub const SOLIDITY: &str = "Solidity";
58pub const YUL: &str = "Yul";
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
62#[non_exhaustive]
63pub enum SolcLanguage {
64 Solidity,
65 Yul,
66}
67
68impl fmt::Display for SolcLanguage {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 Self::Solidity => write!(f, "Solidity"),
72 Self::Yul => write!(f, "Yul"),
73 }
74 }
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize)]
79pub struct SolcInput {
80 pub language: SolcLanguage,
81 pub sources: Sources,
82 pub settings: Settings,
83}
84
85impl Default for SolcInput {
87 fn default() -> Self {
88 Self {
89 language: SolcLanguage::Solidity,
90 sources: Sources::default(),
91 settings: Settings::default(),
92 }
93 }
94}
95
96impl SolcInput {
97 pub fn new(language: SolcLanguage, sources: Sources, mut settings: Settings) -> Self {
98 if language == SolcLanguage::Yul && !settings.remappings.is_empty() {
99 warn!("omitting remappings supplied for the yul sources");
100 settings.remappings = vec![];
101 }
102 Self { language, sources, settings }
103 }
104
105 pub fn resolve_and_build(sources: Sources, settings: Settings) -> Vec<Self> {
108 let mut solidity_sources = Sources::new();
109 let mut yul_sources = Sources::new();
110
111 for (file, source) in sources {
112 if file.extension().is_some_and(|e| e == "yul") {
113 yul_sources.insert(file, source);
114 } else if file.extension().is_some_and(|e| e == "sol") {
115 solidity_sources.insert(file, source);
116 }
117 }
118
119 let mut res = Vec::new();
120
121 if !solidity_sources.is_empty() {
122 res.push(Self::new(SolcLanguage::Solidity, solidity_sources, settings.clone()))
123 }
124
125 if !yul_sources.is_empty() {
126 res.push(Self::new(SolcLanguage::Yul, yul_sources, settings))
127 }
128
129 res
130 }
131
132 pub fn sanitize(&mut self, version: &Version) {
135 self.settings.sanitize(version, self.language);
136 }
137
138 pub fn sanitized(mut self, version: &Version) -> Self {
140 self.settings.sanitize(version, self.language);
141 self
142 }
143
144 #[must_use]
146 pub const fn evm_version(mut self, version: EvmVersion) -> Self {
147 self.settings.evm_version = Some(version);
148 self
149 }
150
151 #[must_use]
153 pub const fn optimizer(mut self, runs: usize) -> Self {
154 self.settings.optimizer.runs(runs);
155 self
156 }
157
158 #[must_use]
160 pub fn join_path(mut self, root: &Path) -> Self {
161 self.sources = self.sources.into_iter().map(|(path, s)| (root.join(path), s)).collect();
162 self
163 }
164
165 pub fn strip_prefix(&mut self, base: &Path) {
167 self.sources = std::mem::take(&mut self.sources)
168 .into_iter()
169 .map(|(path, s)| (strip_prefix_owned(path, base), s))
170 .collect();
171
172 self.settings.strip_prefix(base);
173 }
174
175 pub fn is_yul(&self) -> bool {
178 self.language == SolcLanguage::Yul
179 }
180}
181
182#[derive(Clone, Debug, Serialize, Deserialize)]
189pub struct StandardJsonCompilerInput {
190 pub language: SolcLanguage,
191 #[serde(with = "serde_helpers::tuple_vec_map")]
192 pub sources: Vec<(PathBuf, Source)>,
193 pub settings: Settings,
194}
195
196impl StandardJsonCompilerInput {
199 pub const fn new(sources: Vec<(PathBuf, Source)>, settings: Settings) -> Self {
200 Self { language: SolcLanguage::Solidity, sources, settings }
201 }
202
203 #[must_use]
206 pub fn normalize_evm_version(mut self, version: &Version) -> Self {
207 if let Some(evm_version) = &mut self.settings.evm_version {
208 self.settings.evm_version = evm_version.normalize_version_solc(version);
209 }
210 self
211 }
212}
213
214impl From<StandardJsonCompilerInput> for SolcInput {
215 fn from(input: StandardJsonCompilerInput) -> Self {
216 let StandardJsonCompilerInput { language, sources, settings } = input;
217 Self { language, sources: sources.into_iter().collect(), settings }
218 }
219}
220
221impl From<SolcInput> for StandardJsonCompilerInput {
222 fn from(input: SolcInput) -> Self {
223 let SolcInput { language, sources, settings, .. } = input;
224 Self { language, sources: sources.into_iter().collect(), settings }
225 }
226}
227
228#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct Settings {
231 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub stop_after: Option<String>,
236 #[serde(default, skip_serializing_if = "Vec::is_empty")]
237 pub remappings: Vec<Remapping>,
238 #[serde(default)]
240 pub optimizer: Optimizer,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub model_checker: Option<ModelCheckerSettings>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub metadata: Option<SettingsMetadata>,
247 #[serde(default)]
252 pub output_selection: OutputSelection,
253 #[serde(
254 default,
255 with = "serde_helpers::display_from_str_opt",
256 skip_serializing_if = "Option::is_none"
257 )]
258 pub evm_version: Option<EvmVersion>,
259 #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
262 pub via_ir: Option<bool>,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub debug: Option<DebuggingSettings>,
265 #[serde(default)]
273 pub libraries: Libraries,
274}
275
276impl Settings {
277 pub fn new(output_selection: impl Into<OutputSelection>) -> Self {
279 Self { output_selection: output_selection.into(), ..Default::default() }
280 }
281
282 pub fn sanitized(mut self, version: &Version, language: SolcLanguage) -> Self {
284 self.sanitize(version, language);
285 self
286 }
287
288 pub fn sanitize(&mut self, version: &Version, language: SolcLanguage) {
290 if *version < Version::new(0, 6, 0) {
291 if let Some(meta) = &mut self.metadata {
292 meta.bytecode_hash = None;
295 }
296 self.debug = None;
298 }
299
300 if *version < Version::new(0, 7, 3) {
301 self.stop_after = None;
303 }
304
305 if *version < Version::new(0, 7, 5) {
306 self.via_ir = None;
308 }
309
310 if *version < Version::new(0, 8, 5) {
311 if let Some(optimizer_details) = &mut self.optimizer.details {
313 optimizer_details.inliner = None;
314 }
315 }
316
317 if *version < Version::new(0, 8, 7) {
318 self.model_checker = None;
322 }
323
324 if *version < Version::new(0, 8, 10) {
325 if let Some(debug) = &mut self.debug {
326 debug.debug_info.clear();
329 }
330
331 if let Some(model_checker) = &mut self.model_checker {
332 model_checker.invariants = None;
334 }
335 }
336
337 if *version < Version::new(0, 8, 18) {
338 if let Some(meta) = &mut self.metadata {
340 meta.cbor_metadata = None;
341 }
342
343 if let Some(model_checker) = &mut self.model_checker
344 && let Some(solvers) = &mut model_checker.solvers
345 {
346 solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
348 }
349 }
350
351 if *version < Version::new(0, 8, 20) {
352 if let Some(model_checker) = &mut self.model_checker {
354 model_checker.show_proved_safe = None;
355 model_checker.show_unsupported = None;
356 }
357 }
358
359 if let Some(evm_version) = self.evm_version {
360 self.evm_version = evm_version.normalize_version_solc(version);
361 }
362
363 match language {
364 SolcLanguage::Solidity => {}
365 SolcLanguage::Yul => {
366 if !self.remappings.is_empty() {
367 warn!("omitting remappings supplied for the yul sources");
368 }
369 self.remappings = Vec::new();
370 }
371 }
372 }
373
374 pub fn push_all(&mut self, settings: impl IntoIterator<Item = ContractOutputSelection>) {
376 for value in settings {
377 self.push_output_selection(value)
378 }
379 }
380
381 #[must_use]
383 pub fn with_extra_output(
384 mut self,
385 settings: impl IntoIterator<Item = ContractOutputSelection>,
386 ) -> Self {
387 for value in settings {
388 self.push_output_selection(value)
389 }
390 self
391 }
392
393 pub fn push_output_selection(&mut self, value: impl ToString) {
401 self.push_contract_output_selection("*", value)
402 }
403
404 pub fn push_contract_output_selection(
408 &mut self,
409 contracts: impl Into<String>,
410 value: impl ToString,
411 ) {
412 let value = value.to_string();
413 let values = self
414 .output_selection
415 .as_mut()
416 .entry("*".to_string())
417 .or_default()
418 .entry(contracts.into())
419 .or_default();
420 if !values.contains(&value) {
421 values.push(value)
422 }
423 }
424
425 pub fn set_output_selection(&mut self, values: impl IntoIterator<Item = impl ToString>) {
427 self.set_contract_output_selection("*", values)
428 }
429
430 pub fn set_contract_output_selection(
434 &mut self,
435 key: impl Into<String>,
436 values: impl IntoIterator<Item = impl ToString>,
437 ) {
438 self.output_selection
439 .as_mut()
440 .entry("*".to_string())
441 .or_default()
442 .insert(key.into(), values.into_iter().map(|s| s.to_string()).collect());
443 }
444
445 #[must_use]
447 pub const fn set_via_ir(mut self, via_ir: bool) -> Self {
448 self.via_ir = Some(via_ir);
449 self
450 }
451
452 #[must_use]
454 pub const fn with_via_ir(self) -> Self {
455 self.set_via_ir(true)
456 }
457
458 pub fn with_via_ir_minimum_optimization(mut self) -> Self {
465 self.via_ir = Some(true);
468 self.optimizer.details = Some(OptimizerDetails {
469 peephole: Some(false),
470 inliner: Some(false),
471 jumpdest_remover: Some(false),
472 order_literals: Some(false),
473 deduplicate: Some(false),
474 cse: Some(false),
475 constant_optimizer: Some(false),
476 yul: Some(true), yul_details: Some(YulDetails {
478 stack_allocation: Some(true),
479 optimizer_steps: Some("u".to_string()),
481 }),
482 simple_counter_for_loop_unchecked_increment: None,
484 });
485 self
486 }
487
488 #[must_use]
490 pub fn with_ast(mut self) -> Self {
491 let output = self.output_selection.as_mut().entry("*".to_string()).or_default();
492 output.insert(String::new(), vec!["ast".to_string()]);
493 self
494 }
495
496 pub fn strip_prefix(&mut self, base: &Path) {
497 self.remappings.iter_mut().for_each(|r| {
498 r.strip_prefix(base);
499 });
500
501 self.libraries.libs = std::mem::take(&mut self.libraries.libs)
502 .into_iter()
503 .map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs))
504 .collect();
505
506 self.output_selection = OutputSelection(
507 std::mem::take(&mut self.output_selection.0)
508 .into_iter()
509 .map(|(file, selection)| {
510 (
511 Path::new(&file)
512 .strip_prefix(base)
513 .map(|p| p.display().to_string())
514 .unwrap_or(file),
515 selection,
516 )
517 })
518 .collect(),
519 );
520
521 if let Some(mut model_checker) = self.model_checker.take() {
522 model_checker.contracts = model_checker
523 .contracts
524 .into_iter()
525 .map(|(path, contracts)| {
526 (
527 Path::new(&path)
528 .strip_prefix(base)
529 .map(|p| p.display().to_string())
530 .unwrap_or(path),
531 contracts,
532 )
533 })
534 .collect();
535 self.model_checker = Some(model_checker);
536 }
537 }
538
539 pub fn with_base_path(mut self, base: &Path) -> Self {
541 self.strip_prefix(base);
542 self
543 }
544}
545
546impl Default for Settings {
547 fn default() -> Self {
548 Self {
549 stop_after: None,
550 optimizer: Default::default(),
551 metadata: None,
552 output_selection: OutputSelection::default_output_selection(),
553 evm_version: Some(EvmVersion::default()),
554 via_ir: None,
555 debug: None,
556 libraries: Default::default(),
557 remappings: Default::default(),
558 model_checker: None,
559 }
560 .with_ast()
561 }
562}
563
564#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
566#[serde(transparent)]
567pub struct Libraries {
568 pub libs: BTreeMap<PathBuf, BTreeMap<String, String>>,
570}
571
572impl Libraries {
575 pub fn parse(libs: &[String]) -> Result<Self, SolcError> {
589 let mut libraries = BTreeMap::default();
590 for lib in libs {
591 let mut items = lib.split(':');
592 let file = items.next().ok_or_else(|| {
593 SolcError::msg(format!("failed to parse path to library file: {lib}"))
594 })?;
595 let lib = items
596 .next()
597 .ok_or_else(|| SolcError::msg(format!("failed to parse library name: {lib}")))?;
598 let addr = items
599 .next()
600 .ok_or_else(|| SolcError::msg(format!("failed to parse library address: {lib}")))?;
601 if items.next().is_some() {
602 return Err(SolcError::msg(format!(
603 "failed to parse, too many arguments passed: {lib}"
604 )));
605 }
606 libraries
607 .entry(file.into())
608 .or_insert_with(BTreeMap::default)
609 .insert(lib.to_string(), addr.to_string());
610 }
611 Ok(Self { libs: libraries })
612 }
613
614 pub fn is_empty(&self) -> bool {
615 self.libs.is_empty()
616 }
617
618 pub fn len(&self) -> usize {
619 self.libs.len()
620 }
621
622 pub fn apply<F: FnOnce(Self) -> Self>(self, f: F) -> Self {
624 f(self)
625 }
626
627 pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
630 self.libs = self
631 .libs
632 .into_iter()
633 .map(|(f, l)| (f.strip_prefix(base).unwrap_or(&f).to_path_buf(), l))
634 .collect();
635 self
636 }
637
638 #[allow(clippy::missing_const_for_fn)]
640 pub fn slash_paths(&mut self) {
641 #[cfg(windows)]
642 {
643 use path_slash::PathBufExt;
644
645 self.libs = std::mem::take(&mut self.libs)
646 .into_iter()
647 .map(|(path, libs)| (PathBuf::from(path.to_slash_lossy().as_ref()), libs))
648 .collect()
649 }
650 }
651}
652
653impl From<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
654 fn from(libs: BTreeMap<PathBuf, BTreeMap<String, String>>) -> Self {
655 Self { libs }
656 }
657}
658
659impl AsRef<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
660 fn as_ref(&self) -> &BTreeMap<PathBuf, BTreeMap<String, String>> {
661 &self.libs
662 }
663}
664
665impl AsMut<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
666 fn as_mut(&mut self) -> &mut BTreeMap<PathBuf, BTreeMap<String, String>> {
667 &mut self.libs
668 }
669}
670
671#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
672pub struct Optimizer {
673 #[serde(default, skip_serializing_if = "Option::is_none")]
674 pub enabled: Option<bool>,
675 #[serde(default, skip_serializing_if = "Option::is_none")]
676 pub runs: Option<usize>,
677 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub details: Option<OptimizerDetails>,
682}
683
684impl Optimizer {
685 pub const fn runs(&mut self, runs: usize) {
686 self.runs = Some(runs);
687 }
688
689 pub const fn disable(&mut self) {
690 self.enabled.take();
691 }
692
693 pub const fn enable(&mut self) {
694 self.enabled = Some(true)
695 }
696}
697
698impl Default for Optimizer {
699 fn default() -> Self {
700 Self { enabled: Some(false), runs: Some(200), details: None }
701 }
702}
703
704#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
705#[serde(rename_all = "camelCase")]
706pub struct OptimizerDetails {
707 #[serde(default, skip_serializing_if = "Option::is_none")]
710 pub peephole: Option<bool>,
711 #[serde(default, skip_serializing_if = "Option::is_none")]
714 pub inliner: Option<bool>,
715 #[serde(default, skip_serializing_if = "Option::is_none")]
718 pub jumpdest_remover: Option<bool>,
719 #[serde(default, skip_serializing_if = "Option::is_none")]
721 pub order_literals: Option<bool>,
722 #[serde(default, skip_serializing_if = "Option::is_none")]
724 pub deduplicate: Option<bool>,
725 #[serde(default, skip_serializing_if = "Option::is_none")]
728 pub cse: Option<bool>,
729 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub constant_optimizer: Option<bool>,
732 #[serde(default, skip_serializing_if = "Option::is_none")]
738 pub yul: Option<bool>,
739 #[serde(default, skip_serializing_if = "Option::is_none")]
741 pub yul_details: Option<YulDetails>,
742 #[serde(default, skip_serializing_if = "Option::is_none")]
745 pub simple_counter_for_loop_unchecked_increment: Option<bool>,
746}
747
748impl OptimizerDetails {
751 pub fn is_empty(&self) -> bool {
753 self.peephole.is_none()
754 && self.inliner.is_none()
755 && self.jumpdest_remover.is_none()
756 && self.order_literals.is_none()
757 && self.deduplicate.is_none()
758 && self.cse.is_none()
759 && self.constant_optimizer.is_none()
760 && self.yul.is_none()
761 && self.yul_details.as_ref().map(|yul| yul.is_empty()).unwrap_or(true)
762 && self.simple_counter_for_loop_unchecked_increment.is_none()
763 }
764}
765
766#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
767#[serde(rename_all = "camelCase")]
768pub struct YulDetails {
769 #[serde(default, skip_serializing_if = "Option::is_none")]
772 pub stack_allocation: Option<bool>,
773 #[serde(default, skip_serializing_if = "Option::is_none")]
776 pub optimizer_steps: Option<String>,
777}
778
779impl YulDetails {
782 pub const fn is_empty(&self) -> bool {
784 self.stack_allocation.is_none() && self.optimizer_steps.is_none()
785 }
786}
787
788#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
801pub enum EvmVersion {
802 Homestead,
803 TangerineWhistle,
804 SpuriousDragon,
805 Byzantium,
806 Constantinople,
807 Petersburg,
808 Istanbul,
809 Berlin,
810 London,
811 Paris,
812 Shanghai,
813 Cancun,
814 #[default]
815 Prague,
816 Osaka,
817 Amsterdam,
818}
819
820impl EvmVersion {
821 pub fn default_version_solc(version: &Version) -> Option<Self> {
823 let default = Self::default().normalize_version_solc(version)?;
825
826 match default {
828 Self::Constantinople => {
829 Some(Self::Byzantium)
832 }
833 Self::Cancun if *version == Version::new(0, 8, 24) => {
834 Some(Self::Shanghai)
840 }
841 Self::Prague if *version == Version::new(0, 8, 27) => {
842 Some(Self::Cancun)
844 }
845 _ => Some(default),
846 }
847 }
848
849 pub fn normalize_version_solc(self, version: &Version) -> Option<Self> {
851 (*version >= BYZANTIUM_SOLC).then(|| {
853 if *version >= OSAKA_SOLC {
856 self.min(Self::Osaka)
858 } else if self >= Self::Prague && *version >= PRAGUE_SOLC {
859 Self::Prague
860 } else if self >= Self::Cancun && *version >= CANCUN_SOLC {
861 Self::Cancun
862 } else if self >= Self::Shanghai && *version >= SHANGHAI_SOLC {
863 Self::Shanghai
864 } else if self >= Self::Paris && *version >= PARIS_SOLC {
865 Self::Paris
866 } else if self >= Self::London && *version >= LONDON_SOLC {
867 Self::London
868 } else if self >= Self::Berlin && *version >= BERLIN_SOLC {
869 Self::Berlin
870 } else if self >= Self::Istanbul && *version >= ISTANBUL_SOLC {
871 Self::Istanbul
872 } else if self >= Self::Petersburg && *version >= PETERSBURG_SOLC {
873 Self::Petersburg
874 } else if self >= Self::Constantinople && *version >= CONSTANTINOPLE_SOLC {
875 Self::Constantinople
876 } else if self >= Self::Byzantium {
877 Self::Byzantium
878 } else {
879 self
880 }
881 })
882 }
883
884 pub const fn as_str(&self) -> &'static str {
886 match self {
887 Self::Homestead => "homestead",
888 Self::TangerineWhistle => "tangerineWhistle",
889 Self::SpuriousDragon => "spuriousDragon",
890 Self::Byzantium => "byzantium",
891 Self::Constantinople => "constantinople",
892 Self::Petersburg => "petersburg",
893 Self::Istanbul => "istanbul",
894 Self::Berlin => "berlin",
895 Self::London => "london",
896 Self::Paris => "paris",
897 Self::Shanghai => "shanghai",
898 Self::Cancun => "cancun",
899 Self::Prague => "prague",
900 Self::Osaka => "osaka",
901 Self::Amsterdam => "amsterdam",
902 }
903 }
904
905 pub fn supports_returndata(&self) -> bool {
907 *self >= Self::Byzantium
908 }
909
910 pub fn has_static_call(&self) -> bool {
911 *self >= Self::Byzantium
912 }
913
914 pub fn has_bitwise_shifting(&self) -> bool {
915 *self >= Self::Constantinople
916 }
917
918 pub fn has_create2(&self) -> bool {
919 *self >= Self::Constantinople
920 }
921
922 pub fn has_ext_code_hash(&self) -> bool {
923 *self >= Self::Constantinople
924 }
925
926 pub fn has_chain_id(&self) -> bool {
927 *self >= Self::Istanbul
928 }
929
930 pub fn has_self_balance(&self) -> bool {
931 *self >= Self::Istanbul
932 }
933
934 pub fn has_base_fee(&self) -> bool {
935 *self >= Self::London
936 }
937
938 pub fn has_prevrandao(&self) -> bool {
939 *self >= Self::Paris
940 }
941
942 pub fn has_push0(&self) -> bool {
943 *self >= Self::Shanghai
944 }
945}
946
947impl fmt::Display for EvmVersion {
948 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
949 f.write_str(self.as_str())
950 }
951}
952
953impl FromStr for EvmVersion {
954 type Err = String;
955
956 fn from_str(s: &str) -> Result<Self, Self::Err> {
957 match s {
958 "homestead" => Ok(Self::Homestead),
959 "tangerineWhistle" | "tangerinewhistle" => Ok(Self::TangerineWhistle),
960 "spuriousDragon" | "spuriousdragon" => Ok(Self::SpuriousDragon),
961 "byzantium" => Ok(Self::Byzantium),
962 "constantinople" => Ok(Self::Constantinople),
963 "petersburg" => Ok(Self::Petersburg),
964 "istanbul" => Ok(Self::Istanbul),
965 "berlin" => Ok(Self::Berlin),
966 "london" => Ok(Self::London),
967 "paris" => Ok(Self::Paris),
968 "shanghai" => Ok(Self::Shanghai),
969 "cancun" => Ok(Self::Cancun),
970 "prague" => Ok(Self::Prague),
971 "osaka" => Ok(Self::Osaka),
972 "amsterdam" => Ok(Self::Amsterdam),
973 s => Err(format!("Unknown evm version: {s}")),
974 }
975 }
976}
977
978#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
980#[serde(rename_all = "camelCase")]
981pub struct DebuggingSettings {
982 #[serde(
983 default,
984 with = "serde_helpers::display_from_str_opt",
985 skip_serializing_if = "Option::is_none"
986 )]
987 pub revert_strings: Option<RevertStrings>,
988 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1000 pub debug_info: Vec<String>,
1001}
1002
1003#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1005pub enum RevertStrings {
1006 #[default]
1008 Default,
1009 Strip,
1012 Debug,
1015 VerboseDebug,
1018}
1019
1020impl fmt::Display for RevertStrings {
1021 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1022 let string = match self {
1023 Self::Default => "default",
1024 Self::Strip => "strip",
1025 Self::Debug => "debug",
1026 Self::VerboseDebug => "verboseDebug",
1027 };
1028 write!(f, "{string}")
1029 }
1030}
1031
1032impl FromStr for RevertStrings {
1033 type Err = String;
1034
1035 fn from_str(s: &str) -> Result<Self, Self::Err> {
1036 match s {
1037 "default" => Ok(Self::Default),
1038 "strip" => Ok(Self::Strip),
1039 "debug" => Ok(Self::Debug),
1040 "verboseDebug" | "verbosedebug" => Ok(Self::VerboseDebug),
1041 s => Err(format!("Unknown revert string mode: {s}")),
1042 }
1043 }
1044}
1045
1046#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1047pub struct SettingsMetadata {
1048 #[serde(default, rename = "useLiteralContent", skip_serializing_if = "Option::is_none")]
1050 pub use_literal_content: Option<bool>,
1051 #[serde(
1056 default,
1057 rename = "bytecodeHash",
1058 skip_serializing_if = "Option::is_none",
1059 with = "serde_helpers::display_from_str_opt"
1060 )]
1061 pub bytecode_hash: Option<BytecodeHash>,
1062 #[serde(default, rename = "appendCBOR", skip_serializing_if = "Option::is_none")]
1063 pub cbor_metadata: Option<bool>,
1064}
1065
1066impl SettingsMetadata {
1067 pub const fn new(hash: BytecodeHash, cbor: bool) -> Self {
1068 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: Some(cbor) }
1069 }
1070}
1071
1072impl From<BytecodeHash> for SettingsMetadata {
1073 fn from(hash: BytecodeHash) -> Self {
1074 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: None }
1075 }
1076}
1077
1078#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1082pub enum BytecodeHash {
1083 #[default]
1084 Ipfs,
1085 None,
1086 Bzzr1,
1087}
1088
1089impl FromStr for BytecodeHash {
1090 type Err = String;
1091
1092 fn from_str(s: &str) -> Result<Self, Self::Err> {
1093 match s {
1094 "none" => Ok(Self::None),
1095 "ipfs" => Ok(Self::Ipfs),
1096 "bzzr1" => Ok(Self::Bzzr1),
1097 s => Err(format!("Unknown bytecode hash: {s}")),
1098 }
1099 }
1100}
1101
1102impl fmt::Display for BytecodeHash {
1103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1104 let s = match self {
1105 Self::Ipfs => "ipfs",
1106 Self::None => "none",
1107 Self::Bzzr1 => "bzzr1",
1108 };
1109 f.write_str(s)
1110 }
1111}
1112
1113#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1115pub struct Metadata {
1116 pub compiler: Compiler,
1117 pub language: String,
1118 pub output: Output,
1119 pub settings: MetadataSettings,
1120 pub sources: MetadataSources,
1121 pub version: i64,
1122}
1123
1124#[derive(Clone, Debug, PartialEq, Eq)]
1127pub struct LosslessMetadata {
1128 pub raw_metadata: String,
1130 pub metadata: Metadata,
1132}
1133
1134impl LosslessMetadata {
1137 pub fn raw_json(&self) -> serde_json::Result<serde_json::Value> {
1139 serde_json::from_str(&self.raw_metadata)
1140 }
1141}
1142
1143impl Serialize for LosslessMetadata {
1144 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1145 where
1146 S: Serializer,
1147 {
1148 serializer.serialize_str(&self.raw_metadata)
1149 }
1150}
1151
1152impl<'de> Deserialize<'de> for LosslessMetadata {
1153 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1154 where
1155 D: Deserializer<'de>,
1156 {
1157 struct LosslessMetadataVisitor;
1158
1159 impl Visitor<'_> for LosslessMetadataVisitor {
1160 type Value = LosslessMetadata;
1161
1162 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1163 write!(formatter, "metadata string")
1164 }
1165
1166 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1167 where
1168 E: serde::de::Error,
1169 {
1170 let metadata = serde_json::from_str(value).map_err(serde::de::Error::custom)?;
1171 let raw_metadata = value.to_string();
1172 Ok(LosslessMetadata { raw_metadata, metadata })
1173 }
1174 }
1175 deserializer.deserialize_str(LosslessMetadataVisitor)
1176 }
1177}
1178
1179#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1181pub struct MetadataSettings {
1182 #[serde(default)]
1183 pub remappings: Vec<Remapping>,
1184 pub optimizer: Optimizer,
1185 #[serde(default, skip_serializing_if = "Option::is_none")]
1186 pub metadata: Option<SettingsMetadata>,
1187 #[serde(default, rename = "compilationTarget")]
1190 pub compilation_target: BTreeMap<String, String>,
1191 #[serde(
1193 default,
1194 rename = "evmVersion",
1195 with = "serde_helpers::display_from_str_opt",
1196 skip_serializing_if = "Option::is_none"
1197 )]
1198 pub evm_version: Option<EvmVersion>,
1199 #[serde(default)]
1204 pub libraries: BTreeMap<String, String>,
1205 #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
1208 pub via_ir: Option<bool>,
1209}
1210
1211#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1213pub struct MetadataSources {
1214 #[serde(flatten)]
1215 pub inner: BTreeMap<String, MetadataSource>,
1216}
1217
1218#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1219pub struct MetadataSource {
1220 pub keccak256: String,
1222 #[serde(default)]
1226 pub urls: Vec<String>,
1227 #[serde(default, skip_serializing_if = "Option::is_none")]
1229 pub content: Option<String>,
1230 pub license: Option<String>,
1232}
1233
1234#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1236#[serde(rename_all = "camelCase")]
1237pub struct ModelCheckerSettings {
1238 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1239 pub contracts: BTreeMap<String, Vec<String>>,
1240 #[serde(
1241 default,
1242 with = "serde_helpers::display_from_str_opt",
1243 skip_serializing_if = "Option::is_none"
1244 )]
1245 pub engine: Option<ModelCheckerEngine>,
1246 #[serde(skip_serializing_if = "Option::is_none")]
1247 pub timeout: Option<u32>,
1248 #[serde(skip_serializing_if = "Option::is_none")]
1249 pub targets: Option<Vec<ModelCheckerTarget>>,
1250 #[serde(skip_serializing_if = "Option::is_none")]
1251 pub invariants: Option<Vec<ModelCheckerInvariant>>,
1252 #[serde(skip_serializing_if = "Option::is_none", alias = "show_unproved")]
1253 pub show_unproved: Option<bool>,
1254 #[serde(skip_serializing_if = "Option::is_none", alias = "div_mod_with_slacks")]
1255 pub div_mod_with_slacks: Option<bool>,
1256 #[serde(skip_serializing_if = "Option::is_none")]
1257 pub solvers: Option<Vec<ModelCheckerSolver>>,
1258 #[serde(skip_serializing_if = "Option::is_none", alias = "show_unsupported")]
1259 pub show_unsupported: Option<bool>,
1260 #[serde(skip_serializing_if = "Option::is_none", alias = "show_proved_safe")]
1261 pub show_proved_safe: Option<bool>,
1262}
1263
1264#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1266pub enum ModelCheckerEngine {
1267 #[default]
1268 Default,
1269 All,
1270 BMC,
1271 CHC,
1272}
1273
1274impl fmt::Display for ModelCheckerEngine {
1275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1276 let string = match self {
1277 Self::Default => "none",
1278 Self::All => "all",
1279 Self::BMC => "bmc",
1280 Self::CHC => "chc",
1281 };
1282 write!(f, "{string}")
1283 }
1284}
1285
1286impl FromStr for ModelCheckerEngine {
1287 type Err = String;
1288
1289 fn from_str(s: &str) -> Result<Self, Self::Err> {
1290 match s {
1291 "none" => Ok(Self::Default),
1292 "all" => Ok(Self::All),
1293 "bmc" => Ok(Self::BMC),
1294 "chc" => Ok(Self::CHC),
1295 s => Err(format!("Unknown model checker engine: {s}")),
1296 }
1297 }
1298}
1299
1300#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1302#[serde(rename_all = "camelCase")]
1303pub enum ModelCheckerTarget {
1304 Assert,
1305 Underflow,
1306 Overflow,
1307 DivByZero,
1308 ConstantCondition,
1309 PopEmptyArray,
1310 OutOfBounds,
1311 Balance,
1312}
1313
1314impl fmt::Display for ModelCheckerTarget {
1315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1316 let string = match self {
1317 Self::Assert => "assert",
1318 Self::Underflow => "underflow",
1319 Self::Overflow => "overflow",
1320 Self::DivByZero => "divByZero",
1321 Self::ConstantCondition => "constantCondition",
1322 Self::PopEmptyArray => "popEmptyArray",
1323 Self::OutOfBounds => "outOfBounds",
1324 Self::Balance => "balance",
1325 };
1326 write!(f, "{string}")
1327 }
1328}
1329
1330impl FromStr for ModelCheckerTarget {
1331 type Err = String;
1332
1333 fn from_str(s: &str) -> Result<Self, Self::Err> {
1334 match s {
1335 "assert" => Ok(Self::Assert),
1336 "underflow" => Ok(Self::Underflow),
1337 "overflow" => Ok(Self::Overflow),
1338 "divByZero" => Ok(Self::DivByZero),
1339 "constantCondition" => Ok(Self::ConstantCondition),
1340 "popEmptyArray" => Ok(Self::PopEmptyArray),
1341 "outOfBounds" => Ok(Self::OutOfBounds),
1342 "balance" => Ok(Self::Balance),
1343 s => Err(format!("Unknown model checker target: {s}")),
1344 }
1345 }
1346}
1347
1348#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1350#[serde(rename_all = "camelCase")]
1351pub enum ModelCheckerInvariant {
1352 Contract,
1353 Reentrancy,
1354}
1355
1356impl fmt::Display for ModelCheckerInvariant {
1357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1358 let string = match self {
1359 Self::Contract => "contract",
1360 Self::Reentrancy => "reentrancy",
1361 };
1362 write!(f, "{string}")
1363 }
1364}
1365
1366impl FromStr for ModelCheckerInvariant {
1367 type Err = String;
1368
1369 fn from_str(s: &str) -> Result<Self, Self::Err> {
1370 match s {
1371 "contract" => Ok(Self::Contract),
1372 "reentrancy" => Ok(Self::Reentrancy),
1373 s => Err(format!("Unknown model checker invariant: {s}")),
1374 }
1375 }
1376}
1377
1378#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1380#[serde(rename_all = "camelCase")]
1381pub enum ModelCheckerSolver {
1382 Cvc4,
1383 Eld,
1384 Smtlib2,
1385 Z3,
1386}
1387
1388impl fmt::Display for ModelCheckerSolver {
1389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1390 let string = match self {
1391 Self::Cvc4 => "cvc4",
1392 Self::Eld => "eld",
1393 Self::Smtlib2 => "smtlib2",
1394 Self::Z3 => "z3",
1395 };
1396 write!(f, "{string}")
1397 }
1398}
1399
1400impl FromStr for ModelCheckerSolver {
1401 type Err = String;
1402
1403 fn from_str(s: &str) -> Result<Self, Self::Err> {
1404 match s {
1405 "cvc4" => Ok(Self::Cvc4),
1406 "eld" => Ok(Self::Cvc4),
1407 "smtlib2" => Ok(Self::Smtlib2),
1408 "z3" => Ok(Self::Z3),
1409 s => Err(format!("Unknown model checker invariant: {s}")),
1410 }
1411 }
1412}
1413
1414#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1415pub struct Compiler {
1416 pub version: String,
1417}
1418
1419#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1420pub struct Output {
1421 pub abi: Vec<SolcAbi>,
1422 pub devdoc: Option<Doc>,
1423 pub userdoc: Option<Doc>,
1424}
1425
1426#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1427pub struct SolcAbi {
1428 #[serde(default)]
1429 pub inputs: Vec<Item>,
1430 #[serde(rename = "stateMutability", skip_serializing_if = "Option::is_none")]
1431 pub state_mutability: Option<String>,
1432 #[serde(rename = "type")]
1433 pub abi_type: String,
1434 #[serde(default, skip_serializing_if = "Option::is_none")]
1435 pub name: Option<String>,
1436 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1437 pub outputs: Vec<Item>,
1438 #[serde(default, skip_serializing_if = "Option::is_none")]
1440 pub anonymous: Option<bool>,
1441}
1442
1443#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1444pub struct Item {
1445 #[serde(rename = "internalType")]
1446 pub internal_type: Option<String>,
1447 pub name: String,
1448 #[serde(rename = "type")]
1449 pub put_type: String,
1450 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1451 pub components: Vec<Self>,
1452 #[serde(default, skip_serializing_if = "Option::is_none")]
1454 pub indexed: Option<bool>,
1455}
1456
1457#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1458pub struct Doc {
1459 #[serde(default, skip_serializing_if = "Option::is_none")]
1460 pub kind: Option<String>,
1461 #[serde(default, skip_serializing_if = "Option::is_none")]
1462 pub methods: Option<DocLibraries>,
1463 #[serde(default, skip_serializing_if = "Option::is_none")]
1464 pub version: Option<u32>,
1465}
1466
1467#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1468pub struct DocLibraries {
1469 #[serde(flatten)]
1470 pub libs: BTreeMap<String, serde_json::Value>,
1471}
1472
1473#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1475pub struct CompilerOutput {
1476 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1477 pub errors: Vec<Error>,
1478 #[serde(default)]
1479 pub sources: BTreeMap<PathBuf, SourceFile>,
1480 #[serde(default)]
1481 pub contracts: Contracts,
1482}
1483
1484impl CompilerOutput {
1485 pub fn has_error(&self) -> bool {
1487 self.errors.iter().any(|err| err.severity.is_error())
1488 }
1489
1490 pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1492 self.contracts_iter().find_map(|(name, contract)| {
1493 (name == contract_name).then(|| CompactContractRef::from(contract))
1494 })
1495 }
1496
1497 pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1499 self.contracts.values_mut().find_map(|c| c.remove(contract_name))
1500 }
1501
1502 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1504 self.contracts.values().flatten()
1505 }
1506
1507 pub fn contracts_into_iter(self) -> impl Iterator<Item = (String, Contract)> {
1509 self.contracts.into_values().flatten()
1510 }
1511
1512 pub fn get(&self, path: &Path, contract: &str) -> Option<CompactContractRef<'_>> {
1515 self.contracts
1516 .get(path)
1517 .and_then(|contracts| contracts.get(contract))
1518 .map(CompactContractRef::from)
1519 }
1520
1521 pub fn split(self) -> (SourceFiles, OutputContracts) {
1524 (SourceFiles(self.sources), OutputContracts(self.contracts))
1525 }
1526
1527 pub fn retain_files<'a, I>(&mut self, files: I)
1531 where
1532 I: IntoIterator<Item = &'a Path>,
1533 {
1534 let files: HashSet<_> =
1537 files.into_iter().map(|s| s.to_string_lossy().to_lowercase()).collect();
1538 self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1539 self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1540 }
1541
1542 pub fn merge(&mut self, other: Self) {
1543 self.errors.extend(other.errors);
1544 self.contracts.extend(other.contracts);
1545 self.sources.extend(other.sources);
1546 }
1547}
1548
1549#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1551pub struct OutputContracts(pub Contracts);
1552
1553impl OutputContracts {
1554 pub fn into_contracts(self) -> impl Iterator<Item = (String, Contract)> {
1556 self.0.into_values().flatten()
1557 }
1558
1559 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1561 self.0.values().flatten()
1562 }
1563
1564 pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1566 self.contracts_iter().find_map(|(name, contract)| {
1567 (name == contract_name).then(|| CompactContractRef::from(contract))
1568 })
1569 }
1570
1571 pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1573 self.0.values_mut().find_map(|c| c.remove(contract_name))
1574 }
1575}
1576
1577#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1578pub struct UserDoc {
1579 #[serde(default, skip_serializing_if = "Option::is_none")]
1580 pub version: Option<u32>,
1581 #[serde(default, skip_serializing_if = "Option::is_none")]
1582 pub kind: Option<String>,
1583 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1584 pub methods: BTreeMap<String, UserDocNotice>,
1585 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1586 pub events: BTreeMap<String, UserDocNotice>,
1587 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1588 pub errors: BTreeMap<String, Vec<UserDocNotice>>,
1589 #[serde(default, skip_serializing_if = "Option::is_none")]
1590 pub notice: Option<String>,
1591}
1592
1593#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1594#[serde(untagged)]
1595pub enum UserDocNotice {
1596 Constructor(String),
1598 Notice { notice: String },
1599}
1600
1601#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1602pub struct DevDoc {
1603 #[serde(default, skip_serializing_if = "Option::is_none")]
1604 pub version: Option<u32>,
1605 #[serde(default, skip_serializing_if = "Option::is_none")]
1606 pub kind: Option<String>,
1607 #[serde(default, skip_serializing_if = "Option::is_none")]
1608 pub author: Option<String>,
1609 #[serde(default, skip_serializing_if = "Option::is_none")]
1610 pub details: Option<String>,
1611 #[serde(default, rename = "custom:experimental", skip_serializing_if = "Option::is_none")]
1612 pub custom_experimental: Option<String>,
1613 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1614 pub methods: BTreeMap<String, MethodDoc>,
1615 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1616 pub events: BTreeMap<String, EventDoc>,
1617 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1618 pub errors: BTreeMap<String, Vec<ErrorDoc>>,
1619 #[serde(default, skip_serializing_if = "Option::is_none")]
1620 pub title: Option<String>,
1621}
1622
1623#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1624pub struct MethodDoc {
1625 #[serde(default, skip_serializing_if = "Option::is_none")]
1626 pub details: Option<String>,
1627 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1628 pub params: BTreeMap<String, String>,
1629 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1630 pub returns: BTreeMap<String, String>,
1631}
1632
1633#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1634pub struct EventDoc {
1635 #[serde(default, skip_serializing_if = "Option::is_none")]
1636 pub details: Option<String>,
1637 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1638 pub params: BTreeMap<String, String>,
1639}
1640
1641#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1642pub struct ErrorDoc {
1643 #[serde(default, skip_serializing_if = "Option::is_none")]
1644 pub details: Option<String>,
1645 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1646 pub params: BTreeMap<String, String>,
1647}
1648
1649#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1650#[serde(rename_all = "camelCase")]
1651pub struct Evm {
1652 #[serde(default, skip_serializing_if = "Option::is_none")]
1653 pub assembly: Option<String>,
1654 #[serde(default, skip_serializing_if = "Option::is_none")]
1655 pub legacy_assembly: Option<serde_json::Value>,
1656 pub bytecode: Option<Bytecode>,
1657 #[serde(default, skip_serializing_if = "Option::is_none")]
1658 pub deployed_bytecode: Option<DeployedBytecode>,
1659 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1661 pub method_identifiers: BTreeMap<String, String>,
1662 #[serde(default, skip_serializing_if = "Option::is_none")]
1664 pub gas_estimates: Option<GasEstimates>,
1665}
1666
1667impl Evm {
1668 pub(crate) fn into_compact(self) -> CompactEvm {
1671 let Self {
1672 assembly,
1673 legacy_assembly,
1674 bytecode,
1675 deployed_bytecode,
1676 method_identifiers,
1677 gas_estimates,
1678 } = self;
1679
1680 let (bytecode, deployed_bytecode) = match (bytecode, deployed_bytecode) {
1681 (Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())),
1682 (None, Some(dbcode)) => (None, Some(dbcode.into())),
1683 (Some(bcode), None) => (Some(bcode.into()), None),
1684 (None, None) => (None, None),
1685 };
1686
1687 CompactEvm {
1688 assembly,
1689 legacy_assembly,
1690 bytecode,
1691 deployed_bytecode,
1692 method_identifiers,
1693 gas_estimates,
1694 }
1695 }
1696}
1697
1698#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1699#[serde(rename_all = "camelCase")]
1700pub(crate) struct CompactEvm {
1701 #[serde(default, skip_serializing_if = "Option::is_none")]
1702 pub assembly: Option<String>,
1703 #[serde(default, skip_serializing_if = "Option::is_none")]
1704 pub legacy_assembly: Option<serde_json::Value>,
1705 pub bytecode: Option<CompactBytecode>,
1706 #[serde(default, skip_serializing_if = "Option::is_none")]
1707 pub deployed_bytecode: Option<CompactDeployedBytecode>,
1708 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1710 pub method_identifiers: BTreeMap<String, String>,
1711 #[serde(default, skip_serializing_if = "Option::is_none")]
1713 pub gas_estimates: Option<GasEstimates>,
1714}
1715
1716#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1717#[serde(rename_all = "camelCase")]
1718pub struct FunctionDebugData {
1719 pub entry_point: Option<u32>,
1720 pub id: Option<u32>,
1721 pub parameter_slots: Option<u32>,
1722 pub return_slots: Option<u32>,
1723}
1724
1725#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1726pub struct GeneratedSource {
1727 pub ast: serde_json::Value,
1728 pub contents: String,
1729 pub id: u32,
1730 pub language: String,
1731 pub name: String,
1732}
1733
1734#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1737pub struct Offsets {
1738 pub start: u32,
1739 pub length: u32,
1740}
1741
1742#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1743pub struct GasEstimates {
1744 pub creation: Creation,
1745 #[serde(default)]
1746 pub external: BTreeMap<String, String>,
1747 #[serde(default)]
1748 pub internal: BTreeMap<String, String>,
1749}
1750
1751#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1752#[serde(rename_all = "camelCase")]
1753pub struct Creation {
1754 pub code_deposit_cost: String,
1755 pub execution_cost: String,
1756 pub total_cost: String,
1757}
1758
1759#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1760pub struct Ewasm {
1761 #[serde(default, skip_serializing_if = "Option::is_none")]
1762 pub wast: Option<String>,
1763 pub wasm: String,
1764}
1765
1766#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1768pub struct StorageLayout {
1769 pub storage: Vec<Storage>,
1770 #[serde(default, deserialize_with = "serde_helpers::default_for_null")]
1771 pub types: BTreeMap<String, StorageType>,
1772}
1773
1774impl StorageLayout {
1775 fn is_empty(&self) -> bool {
1776 self.storage.is_empty() && self.types.is_empty()
1777 }
1778}
1779
1780#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1781pub struct Storage {
1782 #[serde(rename = "astId")]
1783 pub ast_id: u64,
1784 pub contract: String,
1785 pub label: String,
1786 pub offset: i64,
1787 pub slot: String,
1788 #[serde(rename = "type")]
1789 pub storage_type: String,
1790}
1791
1792#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1793pub struct StorageType {
1794 pub encoding: String,
1795 #[serde(default, skip_serializing_if = "Option::is_none")]
1796 pub key: Option<String>,
1797 pub label: String,
1798 #[serde(rename = "numberOfBytes")]
1799 pub number_of_bytes: String,
1800 #[serde(default, skip_serializing_if = "Option::is_none")]
1801 pub value: Option<String>,
1802 #[serde(flatten)]
1804 pub other: BTreeMap<String, serde_json::Value>,
1805}
1806
1807#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1808pub struct SourceFile {
1809 pub id: u32,
1810 #[serde(default, with = "serde_helpers::empty_json_object_opt")]
1811 pub ast: Option<Ast>,
1812}
1813
1814impl SourceFile {
1815 pub fn contains_contract_definition(&self) -> bool {
1818 self.ast.as_ref().is_some_and(|ast| {
1819 ast.nodes.iter().any(|node| matches!(node.node_type, NodeType::ContractDefinition))
1820 })
1821 }
1822}
1823
1824#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1826pub struct SourceFiles(pub BTreeMap<PathBuf, SourceFile>);
1827
1828impl SourceFiles {
1829 pub fn into_ids(self) -> impl Iterator<Item = (u32, PathBuf)> {
1831 self.0.into_iter().map(|(k, v)| (v.id, k))
1832 }
1833
1834 pub fn into_paths(self) -> impl Iterator<Item = (PathBuf, u32)> {
1836 self.0.into_iter().map(|(k, v)| (k, v.id))
1837 }
1838}
1839
1840#[cfg(test)]
1841mod tests {
1842 use super::*;
1843 use alloy_primitives::Address;
1844
1845 use std::fs;
1846
1847 #[test]
1848 fn can_link_bytecode() {
1849 #[derive(Serialize, Deserialize)]
1852 struct Mockject {
1853 object: BytecodeObject,
1854 }
1855 fn parse_bytecode(bytecode: &str) -> BytecodeObject {
1856 let object: Mockject =
1857 serde_json::from_value(serde_json::json!({ "object": bytecode })).unwrap();
1858 object.object
1859 }
1860
1861 let bytecode = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1862
1863 let mut object = parse_bytecode(bytecode);
1864 assert!(object.is_unlinked());
1865 assert!(object.contains_placeholder("lib2.sol", "L"));
1866 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1867 assert!(object.link("lib2.sol", "L", Address::random()).resolve().is_some());
1868 assert!(!object.is_unlinked());
1869
1870 let mut code = Bytecode {
1871 function_debug_data: Default::default(),
1872 object: parse_bytecode(bytecode),
1873 opcodes: None,
1874 source_map: None,
1875 generated_sources: vec![],
1876 link_references: BTreeMap::from([(
1877 "lib2.sol".to_string(),
1878 BTreeMap::from([("L".to_string(), vec![])]),
1879 )]),
1880 };
1881
1882 assert!(!code.link("lib2.sol", "Y", Address::random()));
1883 assert!(code.link("lib2.sol", "L", Address::random()));
1884 assert!(code.link("lib2.sol", "L", Address::random()));
1885
1886 let hashed_placeholder = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__$cb901161e812ceb78cfe30ca65050c4337$__6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1887 let mut object = parse_bytecode(hashed_placeholder);
1888 assert!(object.is_unlinked());
1889 assert!(object.contains_placeholder("lib2.sol", "L"));
1890 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1891 assert!(object.link("lib2.sol", "L", Address::default()).resolve().is_some());
1892 assert!(!object.is_unlinked());
1893 }
1894
1895 #[test]
1896 fn can_parse_compiler_output() {
1897 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/out");
1898
1899 for path in fs::read_dir(dir).unwrap() {
1900 let path = path.unwrap().path();
1901 let compiler_output = fs::read_to_string(&path).unwrap();
1902 serde_json::from_str::<CompilerOutput>(&compiler_output).unwrap_or_else(|err| {
1903 panic!("Failed to read compiler output of {} {}", path.display(), err)
1904 });
1905 }
1906 }
1907
1908 #[test]
1909 fn can_parse_compiler_input() {
1910 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1911
1912 for path in fs::read_dir(dir).unwrap() {
1913 let path = path.unwrap().path();
1914 let compiler_input = fs::read_to_string(&path).unwrap();
1915 serde_json::from_str::<SolcInput>(&compiler_input).unwrap_or_else(|err| {
1916 panic!("Failed to read compiler input of {} {}", path.display(), err)
1917 });
1918 }
1919 }
1920
1921 #[test]
1922 fn can_parse_standard_json_compiler_input() {
1923 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1924
1925 for path in fs::read_dir(dir).unwrap() {
1926 let path = path.unwrap().path();
1927 let compiler_input = fs::read_to_string(&path).unwrap();
1928 let val = serde_json::from_str::<StandardJsonCompilerInput>(&compiler_input)
1929 .unwrap_or_else(|err| {
1930 panic!("Failed to read compiler output of {} {}", path.display(), err)
1931 });
1932
1933 let pretty = serde_json::to_string_pretty(&val).unwrap();
1934 serde_json::from_str::<SolcInput>(&pretty).unwrap_or_else(|err| {
1935 panic!("Failed to read converted compiler input of {} {}", path.display(), err)
1936 });
1937 }
1938 }
1939
1940 #[test]
1941 fn test_evm_version_default() {
1942 for &(solc_version, expected) in &[
1943 ("0.4.20", None),
1945 ("0.4.21", Some(EvmVersion::Byzantium)),
1947 ("0.4.22", Some(EvmVersion::Byzantium)),
1949 ("0.5.5", Some(EvmVersion::Petersburg)),
1951 ("0.5.14", Some(EvmVersion::Istanbul)),
1953 ("0.8.5", Some(EvmVersion::Berlin)),
1955 ("0.8.7", Some(EvmVersion::London)),
1957 ("0.8.18", Some(EvmVersion::Paris)),
1959 ("0.8.20", Some(EvmVersion::Shanghai)),
1961 ("0.8.24", Some(EvmVersion::Shanghai)),
1963 ("0.8.25", Some(EvmVersion::Cancun)),
1964 ] {
1965 let version = Version::from_str(solc_version).unwrap();
1966 assert_eq!(
1967 EvmVersion::default_version_solc(&version),
1968 expected,
1969 "({version}, {expected:?})"
1970 )
1971 }
1972 }
1973
1974 #[test]
1975 fn test_evm_version_normalization() {
1976 for &(solc_version, evm_version, expected) in &[
1977 ("0.4.20", EvmVersion::Homestead, None),
1979 ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1981 ("0.4.21", EvmVersion::Constantinople, Some(EvmVersion::Byzantium)),
1982 ("0.4.21", EvmVersion::London, Some(EvmVersion::Byzantium)),
1983 ("0.4.22", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1985 ("0.4.22", EvmVersion::Constantinople, Some(EvmVersion::Constantinople)),
1986 ("0.4.22", EvmVersion::London, Some(EvmVersion::Constantinople)),
1987 ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1989 ("0.5.5", EvmVersion::Petersburg, Some(EvmVersion::Petersburg)),
1990 ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)),
1991 ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1993 ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)),
1994 ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)),
1995 ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1997 ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)),
1998 ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)),
1999 ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2001 ("0.8.7", EvmVersion::London, Some(EvmVersion::London)),
2002 ("0.8.7", EvmVersion::Paris, Some(EvmVersion::London)),
2003 ("0.8.18", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2005 ("0.8.18", EvmVersion::Paris, Some(EvmVersion::Paris)),
2006 ("0.8.18", EvmVersion::Shanghai, Some(EvmVersion::Paris)),
2007 ("0.8.20", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2009 ("0.8.20", EvmVersion::Paris, Some(EvmVersion::Paris)),
2010 ("0.8.20", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2011 ("0.8.20", EvmVersion::Cancun, Some(EvmVersion::Shanghai)),
2012 ("0.8.24", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2014 ("0.8.24", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2015 ("0.8.24", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2016 ("0.8.26", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2018 ("0.8.26", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2019 ("0.8.26", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2020 ("0.8.26", EvmVersion::Prague, Some(EvmVersion::Cancun)),
2021 ("0.8.27", EvmVersion::Prague, Some(EvmVersion::Prague)),
2022 ("0.8.29", EvmVersion::Osaka, Some(EvmVersion::Osaka)),
2023 ("0.8.29", EvmVersion::Amsterdam, Some(EvmVersion::Osaka)),
2025 ] {
2026 let version = Version::from_str(solc_version).unwrap();
2027 assert_eq!(
2028 evm_version.normalize_version_solc(&version),
2029 expected,
2030 "({version}, {evm_version:?})"
2031 )
2032 }
2033 }
2034
2035 #[test]
2036 fn can_sanitize_byte_code_hash() {
2037 let settings = Settings { metadata: Some(BytecodeHash::Ipfs.into()), ..Default::default() };
2038
2039 let input =
2040 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2041
2042 let i = input.clone().sanitized(&Version::new(0, 6, 0));
2043 assert_eq!(i.settings.metadata.unwrap().bytecode_hash, Some(BytecodeHash::Ipfs));
2044
2045 let i = input.sanitized(&Version::new(0, 5, 17));
2046 assert!(i.settings.metadata.unwrap().bytecode_hash.is_none());
2047 }
2048
2049 #[test]
2050 fn can_sanitize_cbor_metadata() {
2051 let settings = Settings {
2052 metadata: Some(SettingsMetadata::new(BytecodeHash::Ipfs, true)),
2053 ..Default::default()
2054 };
2055
2056 let input =
2057 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2058
2059 let i = input.clone().sanitized(&Version::new(0, 8, 18));
2060 assert_eq!(i.settings.metadata.unwrap().cbor_metadata, Some(true));
2061
2062 let i = input.sanitized(&Version::new(0, 8, 0));
2063 assert!(i.settings.metadata.unwrap().cbor_metadata.is_none());
2064 }
2065
2066 #[test]
2067 fn can_parse_libraries() {
2068 let libraries = ["./src/lib/LibraryContract.sol:Library:0xaddress".to_string()];
2069
2070 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2071
2072 assert_eq!(
2073 libs,
2074 BTreeMap::from([(
2075 PathBuf::from("./src/lib/LibraryContract.sol"),
2076 BTreeMap::from([("Library".to_string(), "0xaddress".to_string())])
2077 )])
2078 );
2079 }
2080
2081 #[test]
2082 fn can_strip_libraries_path_prefixes() {
2083 let libraries= [
2084 "/global/root/src/FileInSrc.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2085 "src/deep/DeepFileInSrc.sol:ChainlinkTWAP:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2086 "/global/GlobalFile.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2087 "/global/root/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2088 "test/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2089 ];
2090
2091 let libs = Libraries::parse(&libraries[..])
2092 .unwrap()
2093 .with_stripped_file_prefixes("/global/root".as_ref())
2094 .libs;
2095
2096 assert_eq!(
2097 libs,
2098 BTreeMap::from([
2099 (
2100 PathBuf::from("/global/GlobalFile.sol"),
2101 BTreeMap::from([(
2102 "Math".to_string(),
2103 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2104 )])
2105 ),
2106 (
2107 PathBuf::from("src/FileInSrc.sol"),
2108 BTreeMap::from([(
2109 "Chainlink".to_string(),
2110 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2111 )])
2112 ),
2113 (
2114 PathBuf::from("src/deep/DeepFileInSrc.sol"),
2115 BTreeMap::from([(
2116 "ChainlinkTWAP".to_string(),
2117 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2118 )])
2119 ),
2120 (
2121 PathBuf::from("test/SizeAuctionDiscount.sol"),
2122 BTreeMap::from([(
2123 "Math".to_string(),
2124 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2125 )])
2126 ),
2127 (
2128 PathBuf::from("test/ChainlinkTWAP.t.sol"),
2129 BTreeMap::from([(
2130 "ChainlinkTWAP".to_string(),
2131 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2132 )])
2133 ),
2134 ])
2135 );
2136 }
2137
2138 #[test]
2139 fn can_parse_many_libraries() {
2140 let libraries= [
2141 "./src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2142 "./src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2143 "./src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2144 "./src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2145 "./src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2146 ];
2147
2148 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2149
2150 assert_eq!(
2151 libs,
2152 BTreeMap::from([
2153 (
2154 PathBuf::from("./src/SizeAuctionDiscount.sol"),
2155 BTreeMap::from([
2156 (
2157 "Chainlink".to_string(),
2158 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2159 ),
2160 (
2161 "Math".to_string(),
2162 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2163 )
2164 ])
2165 ),
2166 (
2167 PathBuf::from("./src/SizeAuction.sol"),
2168 BTreeMap::from([
2169 (
2170 "ChainlinkTWAP".to_string(),
2171 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2172 ),
2173 (
2174 "Math".to_string(),
2175 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2176 )
2177 ])
2178 ),
2179 (
2180 PathBuf::from("./src/test/ChainlinkTWAP.t.sol"),
2181 BTreeMap::from([(
2182 "ChainlinkTWAP".to_string(),
2183 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2184 )])
2185 ),
2186 ])
2187 );
2188 }
2189
2190 #[test]
2191 fn test_lossless_metadata() {
2192 #[derive(Debug, Serialize, Deserialize)]
2193 #[serde(rename_all = "camelCase")]
2194 pub struct Contract {
2195 #[serde(
2196 default,
2197 skip_serializing_if = "Option::is_none",
2198 with = "serde_helpers::json_string_opt"
2199 )]
2200 pub metadata: Option<LosslessMetadata>,
2201 }
2202
2203 let s = r#"{"metadata":"{\"compiler\":{\"version\":\"0.4.18+commit.9cf6e910\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{\"transferOwnership(address)\":{\"details\":\"Allows the current owner to transfer control of the contract to a newOwner.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}},\"title\":\"Ownable\"},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"src/Contract.sol\":\"Ownable\"},\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":src/=src/\"]},\"sources\":{\"src/Contract.sol\":{\"keccak256\":\"0x3e0d611f53491f313ae035797ed7ecfd1dfd8db8fef8f82737e6f0cd86d71de7\",\"urls\":[\"bzzr://9c33025fa9d1b8389e4c7c9534a1d70fad91c6c2ad70eb5e4b7dc3a701a5f892\"]}},\"version\":1}"}"#;
2204
2205 let value: serde_json::Value = serde_json::from_str(s).unwrap();
2206 let c: Contract = serde_json::from_value(value).unwrap();
2207 assert_eq!(
2208 c.metadata.as_ref().unwrap().raw_metadata,
2209 "{\"compiler\":{\"version\":\"0.4.18+commit.9cf6e910\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"methods\":{\"transferOwnership(address)\":{\"details\":\"Allows the current owner to transfer control of the contract to a newOwner.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}},\"title\":\"Ownable\"},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"src/Contract.sol\":\"Ownable\"},\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[\":src/=src/\"]},\"sources\":{\"src/Contract.sol\":{\"keccak256\":\"0x3e0d611f53491f313ae035797ed7ecfd1dfd8db8fef8f82737e6f0cd86d71de7\",\"urls\":[\"bzzr://9c33025fa9d1b8389e4c7c9534a1d70fad91c6c2ad70eb5e4b7dc3a701a5f892\"]}},\"version\":1}"
2210 );
2211
2212 let value = serde_json::to_string(&c).unwrap();
2213 assert_eq!(s, value);
2214 }
2215
2216 #[test]
2217 fn test_lossless_storage_layout() {
2218 let input = include_str!("../../../../test-data/foundryissue2462.json").trim();
2219 let layout: StorageLayout = serde_json::from_str(input).unwrap();
2220 assert_eq!(input, &serde_json::to_string(&layout).unwrap());
2221 }
2222
2223 #[test]
2225 fn can_parse_compiler_output_spells_0_6_12() {
2226 let path =
2227 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/0.6.12-with-libs.json");
2228 let content = fs::read_to_string(path).unwrap();
2229 let _output: CompilerOutput = serde_json::from_str(&content).unwrap();
2230 }
2231
2232 #[test]
2234 fn can_sanitize_optimizer_inliner() {
2235 let settings = Settings::default().with_via_ir_minimum_optimization();
2236
2237 let input =
2238 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2239
2240 let i = input.clone().sanitized(&Version::new(0, 8, 4));
2241 assert!(i.settings.optimizer.details.unwrap().inliner.is_none());
2242
2243 let i = input.sanitized(&Version::new(0, 8, 5));
2244 assert_eq!(i.settings.optimizer.details.unwrap().inliner, Some(false));
2245 }
2246
2247 #[test]
2249 fn model_checker_settings_snake_case_aliases() {
2250 let snake_case_json = r#"{
2252 "engine": "chc",
2253 "show_unproved": true,
2254 "show_unsupported": true,
2255 "show_proved_safe": false,
2256 "div_mod_with_slacks": true
2257 }"#;
2258
2259 let settings: ModelCheckerSettings = serde_json::from_str(snake_case_json).unwrap();
2260 assert_eq!(settings.engine, Some(ModelCheckerEngine::CHC));
2261 assert_eq!(settings.show_unproved, Some(true));
2262 assert_eq!(settings.show_unsupported, Some(true));
2263 assert_eq!(settings.show_proved_safe, Some(false));
2264 assert_eq!(settings.div_mod_with_slacks, Some(true));
2265
2266 let camel_case_json = r#"{
2268 "engine": "chc",
2269 "showUnproved": true,
2270 "showUnsupported": true,
2271 "showProvedSafe": false,
2272 "divModWithSlacks": true
2273 }"#;
2274
2275 let settings: ModelCheckerSettings = serde_json::from_str(camel_case_json).unwrap();
2276 assert_eq!(settings.engine, Some(ModelCheckerEngine::CHC));
2277 assert_eq!(settings.show_unproved, Some(true));
2278 assert_eq!(settings.show_unsupported, Some(true));
2279 assert_eq!(settings.show_proved_safe, Some(false));
2280 assert_eq!(settings.div_mod_with_slacks, Some(true));
2281
2282 let serialized = serde_json::to_string(&settings).unwrap();
2284 assert!(serialized.contains("showUnproved"));
2285 assert!(serialized.contains("showUnsupported"));
2286 assert!(serialized.contains("showProvedSafe"));
2287 assert!(serialized.contains("divModWithSlacks"));
2288 assert!(!serialized.contains("show_unproved"));
2289 }
2290}