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