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 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 strip_prefix_owned, BERLIN_SOLC, BYZANTIUM_SOLC, CANCUN_SOLC, CONSTANTINOPLE_SOLC,
41 ISTANBUL_SOLC, LONDON_SOLC, OSAKA_SOLC, PARIS_SOLC, PETERSBURG_SOLC, PRAGUE_SOLC,
42 SHANGHAI_SOLC,
43 },
44};
45pub use serde_helpers::{deserialize_bytes, deserialize_opt_bytes};
46pub use sources::*;
47
48pub type FileToContractsMap<T> = BTreeMap<PathBuf, BTreeMap<String, T>>;
54
55pub type Contracts = FileToContractsMap<Contract>;
57
58pub const SOLIDITY: &str = "Solidity";
59pub const YUL: &str = "Yul";
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
63#[non_exhaustive]
64pub enum SolcLanguage {
65 Solidity,
66 Yul,
67}
68
69impl fmt::Display for SolcLanguage {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 match self {
72 Self::Solidity => write!(f, "Solidity"),
73 Self::Yul => write!(f, "Yul"),
74 }
75 }
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct SolcInput {
81 pub language: SolcLanguage,
82 pub sources: Sources,
83 pub settings: Settings,
84}
85
86impl Default for SolcInput {
88 fn default() -> Self {
89 Self {
90 language: SolcLanguage::Solidity,
91 sources: Sources::default(),
92 settings: Settings::default(),
93 }
94 }
95}
96
97impl SolcInput {
98 pub fn new(language: SolcLanguage, sources: Sources, mut settings: Settings) -> Self {
99 if language == SolcLanguage::Yul && !settings.remappings.is_empty() {
100 warn!("omitting remappings supplied for the yul sources");
101 settings.remappings = vec![];
102 }
103 Self { language, sources, settings }
104 }
105
106 pub fn resolve_and_build(sources: Sources, settings: Settings) -> Vec<Self> {
109 let mut solidity_sources = Sources::new();
110 let mut yul_sources = Sources::new();
111
112 for (file, source) in sources {
113 if file.extension().is_some_and(|e| e == "yul") {
114 yul_sources.insert(file, source);
115 } else if file.extension().is_some_and(|e| e == "sol") {
116 solidity_sources.insert(file, source);
117 }
118 }
119
120 let mut res = Vec::new();
121
122 if !solidity_sources.is_empty() {
123 res.push(Self::new(SolcLanguage::Solidity, solidity_sources, settings.clone()))
124 }
125
126 if !yul_sources.is_empty() {
127 res.push(Self::new(SolcLanguage::Yul, yul_sources, settings))
128 }
129
130 res
131 }
132
133 pub fn sanitize(&mut self, version: &Version) {
136 self.settings.sanitize(version, self.language);
137 }
138
139 pub fn sanitized(mut self, version: &Version) -> Self {
141 self.settings.sanitize(version, self.language);
142 self
143 }
144
145 #[must_use]
147 pub fn evm_version(mut self, version: EvmVersion) -> Self {
148 self.settings.evm_version = Some(version);
149 self
150 }
151
152 #[must_use]
154 pub fn optimizer(mut self, runs: usize) -> Self {
155 self.settings.optimizer.runs(runs);
156 self
157 }
158
159 #[must_use]
161 pub fn join_path(mut self, root: &Path) -> Self {
162 self.sources = self.sources.into_iter().map(|(path, s)| (root.join(path), s)).collect();
163 self
164 }
165
166 pub fn strip_prefix(&mut self, base: &Path) {
168 self.sources = std::mem::take(&mut self.sources)
169 .into_iter()
170 .map(|(path, s)| (strip_prefix_owned(path, base), s))
171 .collect();
172
173 self.settings.strip_prefix(base);
174 }
175
176 pub fn is_yul(&self) -> bool {
179 self.language == SolcLanguage::Yul
180 }
181}
182
183#[derive(Clone, Debug, Serialize, Deserialize)]
190pub struct StandardJsonCompilerInput {
191 pub language: SolcLanguage,
192 #[serde(with = "serde_helpers::tuple_vec_map")]
193 pub sources: Vec<(PathBuf, Source)>,
194 pub settings: Settings,
195}
196
197impl StandardJsonCompilerInput {
200 pub fn new(sources: Vec<(PathBuf, Source)>, settings: Settings) -> Self {
201 Self { language: SolcLanguage::Solidity, sources, settings }
202 }
203
204 #[must_use]
207 pub fn normalize_evm_version(mut self, version: &Version) -> Self {
208 if let Some(evm_version) = &mut self.settings.evm_version {
209 self.settings.evm_version = evm_version.normalize_version_solc(version);
210 }
211 self
212 }
213}
214
215impl From<StandardJsonCompilerInput> for SolcInput {
216 fn from(input: StandardJsonCompilerInput) -> Self {
217 let StandardJsonCompilerInput { language, sources, settings } = input;
218 Self { language, sources: sources.into_iter().collect(), settings }
219 }
220}
221
222impl From<SolcInput> for StandardJsonCompilerInput {
223 fn from(input: SolcInput) -> Self {
224 let SolcInput { language, sources, settings, .. } = input;
225 Self { language, sources: sources.into_iter().collect(), settings }
226 }
227}
228
229#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct Settings {
232 #[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}
276
277impl Settings {
278 pub fn new(output_selection: impl Into<OutputSelection>) -> Self {
280 Self { output_selection: output_selection.into(), ..Default::default() }
281 }
282
283 pub fn sanitized(mut self, version: &Version, language: SolcLanguage) -> Self {
285 self.sanitize(version, language);
286 self
287 }
288
289 pub fn sanitize(&mut self, version: &Version, language: SolcLanguage) {
291 if *version < Version::new(0, 6, 0) {
292 if let Some(meta) = &mut self.metadata {
293 meta.bytecode_hash = None;
296 }
297 self.debug = None;
299 }
300
301 if *version < Version::new(0, 7, 3) {
302 self.stop_after = None;
304 }
305
306 if *version < Version::new(0, 7, 5) {
307 self.via_ir = None;
309 }
310
311 if *version < Version::new(0, 8, 5) {
312 if let Some(optimizer_details) = &mut self.optimizer.details {
314 optimizer_details.inliner = None;
315 }
316 }
317
318 if *version < Version::new(0, 8, 7) {
319 self.model_checker = None;
323 }
324
325 if *version < Version::new(0, 8, 10) {
326 if let Some(debug) = &mut self.debug {
327 debug.debug_info.clear();
330 }
331
332 if let Some(model_checker) = &mut self.model_checker {
333 model_checker.invariants = None;
335 }
336 }
337
338 if *version < Version::new(0, 8, 18) {
339 if let Some(meta) = &mut self.metadata {
341 meta.cbor_metadata = None;
342 }
343
344 if let Some(model_checker) = &mut self.model_checker {
345 if let Some(solvers) = &mut model_checker.solvers {
346 solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
348 }
349 }
350 }
351
352 if *version < Version::new(0, 8, 20) {
353 if let Some(model_checker) = &mut self.model_checker {
355 model_checker.show_proved_safe = None;
356 model_checker.show_unsupported = None;
357 }
358 }
359
360 if let Some(evm_version) = self.evm_version {
361 self.evm_version = evm_version.normalize_version_solc(version);
362 }
363
364 match language {
365 SolcLanguage::Solidity => {}
366 SolcLanguage::Yul => {
367 if !self.remappings.is_empty() {
368 warn!("omitting remappings supplied for the yul sources");
369 }
370 self.remappings = Vec::new();
371 }
372 }
373 }
374
375 pub fn push_all(&mut self, settings: impl IntoIterator<Item = ContractOutputSelection>) {
377 for value in settings {
378 self.push_output_selection(value)
379 }
380 }
381
382 #[must_use]
384 pub fn with_extra_output(
385 mut self,
386 settings: impl IntoIterator<Item = ContractOutputSelection>,
387 ) -> Self {
388 for value in settings {
389 self.push_output_selection(value)
390 }
391 self
392 }
393
394 pub fn push_output_selection(&mut self, value: impl ToString) {
402 self.push_contract_output_selection("*", value)
403 }
404
405 pub fn push_contract_output_selection(
409 &mut self,
410 contracts: impl Into<String>,
411 value: impl ToString,
412 ) {
413 let value = value.to_string();
414 let values = self
415 .output_selection
416 .as_mut()
417 .entry("*".to_string())
418 .or_default()
419 .entry(contracts.into())
420 .or_default();
421 if !values.contains(&value) {
422 values.push(value)
423 }
424 }
425
426 pub fn set_output_selection(&mut self, values: impl IntoIterator<Item = impl ToString>) {
428 self.set_contract_output_selection("*", values)
429 }
430
431 pub fn set_contract_output_selection(
435 &mut self,
436 key: impl Into<String>,
437 values: impl IntoIterator<Item = impl ToString>,
438 ) {
439 self.output_selection
440 .as_mut()
441 .entry("*".to_string())
442 .or_default()
443 .insert(key.into(), values.into_iter().map(|s| s.to_string()).collect());
444 }
445
446 #[must_use]
448 pub fn set_via_ir(mut self, via_ir: bool) -> Self {
449 self.via_ir = Some(via_ir);
450 self
451 }
452
453 #[must_use]
455 pub fn with_via_ir(self) -> Self {
456 self.set_via_ir(true)
457 }
458
459 pub fn with_via_ir_minimum_optimization(mut self) -> Self {
466 self.via_ir = Some(true);
469 self.optimizer.details = Some(OptimizerDetails {
470 peephole: Some(false),
471 inliner: Some(false),
472 jumpdest_remover: Some(false),
473 order_literals: Some(false),
474 deduplicate: Some(false),
475 cse: Some(false),
476 constant_optimizer: Some(false),
477 yul: Some(true), yul_details: Some(YulDetails {
479 stack_allocation: Some(true),
480 optimizer_steps: Some("u".to_string()),
482 }),
483 simple_counter_for_loop_unchecked_increment: None,
485 });
486 self
487 }
488
489 #[must_use]
491 pub fn with_ast(mut self) -> Self {
492 let output = self.output_selection.as_mut().entry("*".to_string()).or_default();
493 output.insert(String::new(), vec!["ast".to_string()]);
494 self
495 }
496
497 pub fn strip_prefix(&mut self, base: &Path) {
498 self.remappings.iter_mut().for_each(|r| {
499 r.strip_prefix(base);
500 });
501
502 self.libraries.libs = std::mem::take(&mut self.libraries.libs)
503 .into_iter()
504 .map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs))
505 .collect();
506
507 self.output_selection = OutputSelection(
508 std::mem::take(&mut self.output_selection.0)
509 .into_iter()
510 .map(|(file, selection)| {
511 (
512 Path::new(&file)
513 .strip_prefix(base)
514 .map(|p| p.display().to_string())
515 .unwrap_or(file),
516 selection,
517 )
518 })
519 .collect(),
520 );
521
522 if let Some(mut model_checker) = self.model_checker.take() {
523 model_checker.contracts = model_checker
524 .contracts
525 .into_iter()
526 .map(|(path, contracts)| {
527 (
528 Path::new(&path)
529 .strip_prefix(base)
530 .map(|p| p.display().to_string())
531 .unwrap_or(path),
532 contracts,
533 )
534 })
535 .collect();
536 self.model_checker = Some(model_checker);
537 }
538 }
539
540 pub fn with_base_path(mut self, base: &Path) -> Self {
542 self.strip_prefix(base);
543 self
544 }
545}
546
547impl Default for Settings {
548 fn default() -> Self {
549 Self {
550 stop_after: None,
551 optimizer: Default::default(),
552 metadata: None,
553 output_selection: OutputSelection::default_output_selection(),
554 evm_version: Some(EvmVersion::default()),
555 via_ir: None,
556 debug: None,
557 libraries: Default::default(),
558 remappings: Default::default(),
559 model_checker: None,
560 }
561 .with_ast()
562 }
563}
564
565#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
567#[serde(transparent)]
568pub struct Libraries {
569 pub libs: BTreeMap<PathBuf, BTreeMap<String, String>>,
571}
572
573impl Libraries {
576 pub fn parse(libs: &[String]) -> Result<Self, SolcError> {
590 let mut libraries = BTreeMap::default();
591 for lib in libs {
592 let mut items = lib.split(':');
593 let file = items.next().ok_or_else(|| {
594 SolcError::msg(format!("failed to parse path to library file: {lib}"))
595 })?;
596 let lib = items
597 .next()
598 .ok_or_else(|| SolcError::msg(format!("failed to parse library name: {lib}")))?;
599 let addr = items
600 .next()
601 .ok_or_else(|| SolcError::msg(format!("failed to parse library address: {lib}")))?;
602 if items.next().is_some() {
603 return Err(SolcError::msg(format!(
604 "failed to parse, too many arguments passed: {lib}"
605 )));
606 }
607 libraries
608 .entry(file.into())
609 .or_insert_with(BTreeMap::default)
610 .insert(lib.to_string(), addr.to_string());
611 }
612 Ok(Self { libs: libraries })
613 }
614
615 pub fn is_empty(&self) -> bool {
616 self.libs.is_empty()
617 }
618
619 pub fn len(&self) -> usize {
620 self.libs.len()
621 }
622
623 pub fn apply<F: FnOnce(Self) -> Self>(self, f: F) -> Self {
625 f(self)
626 }
627
628 pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
631 self.libs = self
632 .libs
633 .into_iter()
634 .map(|(f, l)| (f.strip_prefix(base).unwrap_or(&f).to_path_buf(), l))
635 .collect();
636 self
637 }
638
639 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 fn runs(&mut self, runs: usize) {
686 self.runs = Some(runs);
687 }
688
689 pub fn disable(&mut self) {
690 self.enabled.take();
691 }
692
693 pub 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 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}
818
819impl EvmVersion {
820 pub fn default_version_solc(version: &Version) -> Option<Self> {
822 let default = Self::default().normalize_version_solc(version)?;
824
825 match default {
827 Self::Constantinople => {
828 Some(Self::Byzantium)
831 }
832 Self::Cancun if *version == Version::new(0, 8, 24) => {
833 Some(Self::Shanghai)
839 }
840 Self::Prague if *version == Version::new(0, 8, 27) => {
841 Some(Self::Cancun)
843 }
844 _ => Some(default),
845 }
846 }
847
848 pub fn normalize_version_solc(self, version: &Version) -> Option<Self> {
850 if *version >= BYZANTIUM_SOLC {
852 let normalized = if *version >= OSAKA_SOLC {
855 self
856 } else if self >= Self::Prague && *version >= PRAGUE_SOLC {
857 Self::Prague
858 } else if self >= Self::Cancun && *version >= CANCUN_SOLC {
859 Self::Cancun
860 } else if self >= Self::Shanghai && *version >= SHANGHAI_SOLC {
861 Self::Shanghai
862 } else if self >= Self::Paris && *version >= PARIS_SOLC {
863 Self::Paris
864 } else if self >= Self::London && *version >= LONDON_SOLC {
865 Self::London
866 } else if self >= Self::Berlin && *version >= BERLIN_SOLC {
867 Self::Berlin
868 } else if self >= Self::Istanbul && *version >= ISTANBUL_SOLC {
869 Self::Istanbul
870 } else if self >= Self::Petersburg && *version >= PETERSBURG_SOLC {
871 Self::Petersburg
872 } else if self >= Self::Constantinople && *version >= CONSTANTINOPLE_SOLC {
873 Self::Constantinople
874 } else if self >= Self::Byzantium {
875 Self::Byzantium
876 } else {
877 self
878 };
879 Some(normalized)
880 } else {
881 None
882 }
883 }
884
885 pub const fn as_str(&self) -> &'static str {
887 match self {
888 Self::Homestead => "homestead",
889 Self::TangerineWhistle => "tangerineWhistle",
890 Self::SpuriousDragon => "spuriousDragon",
891 Self::Byzantium => "byzantium",
892 Self::Constantinople => "constantinople",
893 Self::Petersburg => "petersburg",
894 Self::Istanbul => "istanbul",
895 Self::Berlin => "berlin",
896 Self::London => "london",
897 Self::Paris => "paris",
898 Self::Shanghai => "shanghai",
899 Self::Cancun => "cancun",
900 Self::Prague => "prague",
901 Self::Osaka => "osaka",
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 s => Err(format!("Unknown evm version: {s}")),
973 }
974 }
975}
976
977#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
979#[serde(rename_all = "camelCase")]
980pub struct DebuggingSettings {
981 #[serde(
982 default,
983 with = "serde_helpers::display_from_str_opt",
984 skip_serializing_if = "Option::is_none"
985 )]
986 pub revert_strings: Option<RevertStrings>,
987 #[serde(default, skip_serializing_if = "Vec::is_empty")]
999 pub debug_info: Vec<String>,
1000}
1001
1002#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1004pub enum RevertStrings {
1005 #[default]
1007 Default,
1008 Strip,
1011 Debug,
1014 VerboseDebug,
1017}
1018
1019impl fmt::Display for RevertStrings {
1020 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1021 let string = match self {
1022 Self::Default => "default",
1023 Self::Strip => "strip",
1024 Self::Debug => "debug",
1025 Self::VerboseDebug => "verboseDebug",
1026 };
1027 write!(f, "{string}")
1028 }
1029}
1030
1031impl FromStr for RevertStrings {
1032 type Err = String;
1033
1034 fn from_str(s: &str) -> Result<Self, Self::Err> {
1035 match s {
1036 "default" => Ok(Self::Default),
1037 "strip" => Ok(Self::Strip),
1038 "debug" => Ok(Self::Debug),
1039 "verboseDebug" | "verbosedebug" => Ok(Self::VerboseDebug),
1040 s => Err(format!("Unknown revert string mode: {s}")),
1041 }
1042 }
1043}
1044
1045#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1046pub struct SettingsMetadata {
1047 #[serde(default, rename = "useLiteralContent", skip_serializing_if = "Option::is_none")]
1049 pub use_literal_content: Option<bool>,
1050 #[serde(
1055 default,
1056 rename = "bytecodeHash",
1057 skip_serializing_if = "Option::is_none",
1058 with = "serde_helpers::display_from_str_opt"
1059 )]
1060 pub bytecode_hash: Option<BytecodeHash>,
1061 #[serde(default, rename = "appendCBOR", skip_serializing_if = "Option::is_none")]
1062 pub cbor_metadata: Option<bool>,
1063}
1064
1065impl SettingsMetadata {
1066 pub fn new(hash: BytecodeHash, cbor: bool) -> Self {
1067 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: Some(cbor) }
1068 }
1069}
1070
1071impl From<BytecodeHash> for SettingsMetadata {
1072 fn from(hash: BytecodeHash) -> Self {
1073 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: None }
1074 }
1075}
1076
1077#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1081pub enum BytecodeHash {
1082 #[default]
1083 Ipfs,
1084 None,
1085 Bzzr1,
1086}
1087
1088impl FromStr for BytecodeHash {
1089 type Err = String;
1090
1091 fn from_str(s: &str) -> Result<Self, Self::Err> {
1092 match s {
1093 "none" => Ok(Self::None),
1094 "ipfs" => Ok(Self::Ipfs),
1095 "bzzr1" => Ok(Self::Bzzr1),
1096 s => Err(format!("Unknown bytecode hash: {s}")),
1097 }
1098 }
1099}
1100
1101impl fmt::Display for BytecodeHash {
1102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1103 let s = match self {
1104 Self::Ipfs => "ipfs",
1105 Self::None => "none",
1106 Self::Bzzr1 => "bzzr1",
1107 };
1108 f.write_str(s)
1109 }
1110}
1111
1112#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1114pub struct Metadata {
1115 pub compiler: Compiler,
1116 pub language: String,
1117 pub output: Output,
1118 pub settings: MetadataSettings,
1119 pub sources: MetadataSources,
1120 pub version: i64,
1121}
1122
1123#[derive(Clone, Debug, PartialEq, Eq)]
1126pub struct LosslessMetadata {
1127 pub raw_metadata: String,
1129 pub metadata: Metadata,
1131}
1132
1133impl LosslessMetadata {
1136 pub fn raw_json(&self) -> serde_json::Result<serde_json::Value> {
1138 serde_json::from_str(&self.raw_metadata)
1139 }
1140}
1141
1142impl Serialize for LosslessMetadata {
1143 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1144 where
1145 S: Serializer,
1146 {
1147 serializer.serialize_str(&self.raw_metadata)
1148 }
1149}
1150
1151impl<'de> Deserialize<'de> for LosslessMetadata {
1152 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1153 where
1154 D: Deserializer<'de>,
1155 {
1156 struct LosslessMetadataVisitor;
1157
1158 impl Visitor<'_> for LosslessMetadataVisitor {
1159 type Value = LosslessMetadata;
1160
1161 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1162 write!(formatter, "metadata string")
1163 }
1164
1165 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1166 where
1167 E: serde::de::Error,
1168 {
1169 let metadata = serde_json::from_str(value).map_err(serde::de::Error::custom)?;
1170 let raw_metadata = value.to_string();
1171 Ok(LosslessMetadata { raw_metadata, metadata })
1172 }
1173 }
1174 deserializer.deserialize_str(LosslessMetadataVisitor)
1175 }
1176}
1177
1178#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1180pub struct MetadataSettings {
1181 #[serde(default)]
1182 pub remappings: Vec<Remapping>,
1183 pub optimizer: Optimizer,
1184 #[serde(default, skip_serializing_if = "Option::is_none")]
1185 pub metadata: Option<SettingsMetadata>,
1186 #[serde(default, rename = "compilationTarget")]
1189 pub compilation_target: BTreeMap<String, String>,
1190 #[serde(
1192 default,
1193 rename = "evmVersion",
1194 with = "serde_helpers::display_from_str_opt",
1195 skip_serializing_if = "Option::is_none"
1196 )]
1197 pub evm_version: Option<EvmVersion>,
1198 #[serde(default)]
1203 pub libraries: BTreeMap<String, String>,
1204 #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
1207 pub via_ir: Option<bool>,
1208}
1209
1210#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1212pub struct MetadataSources {
1213 #[serde(flatten)]
1214 pub inner: BTreeMap<String, MetadataSource>,
1215}
1216
1217#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1218pub struct MetadataSource {
1219 pub keccak256: String,
1221 #[serde(default)]
1225 pub urls: Vec<String>,
1226 #[serde(default, skip_serializing_if = "Option::is_none")]
1228 pub content: Option<String>,
1229 pub license: Option<String>,
1231}
1232
1233#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1235#[serde(rename_all = "camelCase")]
1236pub struct ModelCheckerSettings {
1237 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1238 pub contracts: BTreeMap<String, Vec<String>>,
1239 #[serde(
1240 default,
1241 with = "serde_helpers::display_from_str_opt",
1242 skip_serializing_if = "Option::is_none"
1243 )]
1244 pub engine: Option<ModelCheckerEngine>,
1245 #[serde(skip_serializing_if = "Option::is_none")]
1246 pub timeout: Option<u32>,
1247 #[serde(skip_serializing_if = "Option::is_none")]
1248 pub targets: Option<Vec<ModelCheckerTarget>>,
1249 #[serde(skip_serializing_if = "Option::is_none")]
1250 pub invariants: Option<Vec<ModelCheckerInvariant>>,
1251 #[serde(skip_serializing_if = "Option::is_none")]
1252 pub show_unproved: Option<bool>,
1253 #[serde(skip_serializing_if = "Option::is_none")]
1254 pub div_mod_with_slacks: Option<bool>,
1255 #[serde(skip_serializing_if = "Option::is_none")]
1256 pub solvers: Option<Vec<ModelCheckerSolver>>,
1257 #[serde(skip_serializing_if = "Option::is_none")]
1258 pub show_unsupported: Option<bool>,
1259 #[serde(skip_serializing_if = "Option::is_none")]
1260 pub show_proved_safe: Option<bool>,
1261}
1262
1263#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1265pub enum ModelCheckerEngine {
1266 #[default]
1267 Default,
1268 All,
1269 BMC,
1270 CHC,
1271}
1272
1273impl fmt::Display for ModelCheckerEngine {
1274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1275 let string = match self {
1276 Self::Default => "none",
1277 Self::All => "all",
1278 Self::BMC => "bmc",
1279 Self::CHC => "chc",
1280 };
1281 write!(f, "{string}")
1282 }
1283}
1284
1285impl FromStr for ModelCheckerEngine {
1286 type Err = String;
1287
1288 fn from_str(s: &str) -> Result<Self, Self::Err> {
1289 match s {
1290 "none" => Ok(Self::Default),
1291 "all" => Ok(Self::All),
1292 "bmc" => Ok(Self::BMC),
1293 "chc" => Ok(Self::CHC),
1294 s => Err(format!("Unknown model checker engine: {s}")),
1295 }
1296 }
1297}
1298
1299#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1301#[serde(rename_all = "camelCase")]
1302pub enum ModelCheckerTarget {
1303 Assert,
1304 Underflow,
1305 Overflow,
1306 DivByZero,
1307 ConstantCondition,
1308 PopEmptyArray,
1309 OutOfBounds,
1310 Balance,
1311}
1312
1313impl fmt::Display for ModelCheckerTarget {
1314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1315 let string = match self {
1316 Self::Assert => "assert",
1317 Self::Underflow => "underflow",
1318 Self::Overflow => "overflow",
1319 Self::DivByZero => "divByZero",
1320 Self::ConstantCondition => "constantCondition",
1321 Self::PopEmptyArray => "popEmptyArray",
1322 Self::OutOfBounds => "outOfBounds",
1323 Self::Balance => "balance",
1324 };
1325 write!(f, "{string}")
1326 }
1327}
1328
1329impl FromStr for ModelCheckerTarget {
1330 type Err = String;
1331
1332 fn from_str(s: &str) -> Result<Self, Self::Err> {
1333 match s {
1334 "assert" => Ok(Self::Assert),
1335 "underflow" => Ok(Self::Underflow),
1336 "overflow" => Ok(Self::Overflow),
1337 "divByZero" => Ok(Self::DivByZero),
1338 "constantCondition" => Ok(Self::ConstantCondition),
1339 "popEmptyArray" => Ok(Self::PopEmptyArray),
1340 "outOfBounds" => Ok(Self::OutOfBounds),
1341 "balance" => Ok(Self::Balance),
1342 s => Err(format!("Unknown model checker target: {s}")),
1343 }
1344 }
1345}
1346
1347#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1349#[serde(rename_all = "camelCase")]
1350pub enum ModelCheckerInvariant {
1351 Contract,
1352 Reentrancy,
1353}
1354
1355impl fmt::Display for ModelCheckerInvariant {
1356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1357 let string = match self {
1358 Self::Contract => "contract",
1359 Self::Reentrancy => "reentrancy",
1360 };
1361 write!(f, "{string}")
1362 }
1363}
1364
1365impl FromStr for ModelCheckerInvariant {
1366 type Err = String;
1367
1368 fn from_str(s: &str) -> Result<Self, Self::Err> {
1369 match s {
1370 "contract" => Ok(Self::Contract),
1371 "reentrancy" => Ok(Self::Reentrancy),
1372 s => Err(format!("Unknown model checker invariant: {s}")),
1373 }
1374 }
1375}
1376
1377#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1379#[serde(rename_all = "camelCase")]
1380pub enum ModelCheckerSolver {
1381 Cvc4,
1382 Eld,
1383 Smtlib2,
1384 Z3,
1385}
1386
1387impl fmt::Display for ModelCheckerSolver {
1388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1389 let string = match self {
1390 Self::Cvc4 => "cvc4",
1391 Self::Eld => "eld",
1392 Self::Smtlib2 => "smtlib2",
1393 Self::Z3 => "z3",
1394 };
1395 write!(f, "{string}")
1396 }
1397}
1398
1399impl FromStr for ModelCheckerSolver {
1400 type Err = String;
1401
1402 fn from_str(s: &str) -> Result<Self, Self::Err> {
1403 match s {
1404 "cvc4" => Ok(Self::Cvc4),
1405 "eld" => Ok(Self::Cvc4),
1406 "smtlib2" => Ok(Self::Smtlib2),
1407 "z3" => Ok(Self::Z3),
1408 s => Err(format!("Unknown model checker invariant: {s}")),
1409 }
1410 }
1411}
1412
1413#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1414pub struct Compiler {
1415 pub version: String,
1416}
1417
1418#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1419pub struct Output {
1420 pub abi: Vec<SolcAbi>,
1421 pub devdoc: Option<Doc>,
1422 pub userdoc: Option<Doc>,
1423}
1424
1425#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1426pub struct SolcAbi {
1427 #[serde(default)]
1428 pub inputs: Vec<Item>,
1429 #[serde(rename = "stateMutability", skip_serializing_if = "Option::is_none")]
1430 pub state_mutability: Option<String>,
1431 #[serde(rename = "type")]
1432 pub abi_type: String,
1433 #[serde(default, skip_serializing_if = "Option::is_none")]
1434 pub name: Option<String>,
1435 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1436 pub outputs: Vec<Item>,
1437 #[serde(default, skip_serializing_if = "Option::is_none")]
1439 pub anonymous: Option<bool>,
1440}
1441
1442#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1443pub struct Item {
1444 #[serde(rename = "internalType")]
1445 pub internal_type: Option<String>,
1446 pub name: String,
1447 #[serde(rename = "type")]
1448 pub put_type: String,
1449 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1450 pub components: Vec<Item>,
1451 #[serde(default, skip_serializing_if = "Option::is_none")]
1453 pub indexed: Option<bool>,
1454}
1455
1456#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1457pub struct Doc {
1458 #[serde(default, skip_serializing_if = "Option::is_none")]
1459 pub kind: Option<String>,
1460 #[serde(default, skip_serializing_if = "Option::is_none")]
1461 pub methods: Option<DocLibraries>,
1462 #[serde(default, skip_serializing_if = "Option::is_none")]
1463 pub version: Option<u32>,
1464}
1465
1466#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1467pub struct DocLibraries {
1468 #[serde(flatten)]
1469 pub libs: BTreeMap<String, serde_json::Value>,
1470}
1471
1472#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1474pub struct CompilerOutput {
1475 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1476 pub errors: Vec<Error>,
1477 #[serde(default)]
1478 pub sources: BTreeMap<PathBuf, SourceFile>,
1479 #[serde(default)]
1480 pub contracts: Contracts,
1481}
1482
1483impl CompilerOutput {
1484 pub fn has_error(&self) -> bool {
1486 self.errors.iter().any(|err| err.severity.is_error())
1487 }
1488
1489 pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1491 self.contracts_iter().find_map(|(name, contract)| {
1492 (name == contract_name).then(|| CompactContractRef::from(contract))
1493 })
1494 }
1495
1496 pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1498 self.contracts.values_mut().find_map(|c| c.remove(contract_name))
1499 }
1500
1501 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1503 self.contracts.values().flatten()
1504 }
1505
1506 pub fn contracts_into_iter(self) -> impl Iterator<Item = (String, Contract)> {
1508 self.contracts.into_values().flatten()
1509 }
1510
1511 pub fn get(&self, path: &Path, contract: &str) -> Option<CompactContractRef<'_>> {
1514 self.contracts
1515 .get(path)
1516 .and_then(|contracts| contracts.get(contract))
1517 .map(CompactContractRef::from)
1518 }
1519
1520 pub fn split(self) -> (SourceFiles, OutputContracts) {
1523 (SourceFiles(self.sources), OutputContracts(self.contracts))
1524 }
1525
1526 pub fn retain_files<'a, I>(&mut self, files: I)
1530 where
1531 I: IntoIterator<Item = &'a Path>,
1532 {
1533 let files: HashSet<_> =
1536 files.into_iter().map(|s| s.to_string_lossy().to_lowercase()).collect();
1537 self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1538 self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1539 }
1540
1541 pub fn merge(&mut self, other: Self) {
1542 self.errors.extend(other.errors);
1543 self.contracts.extend(other.contracts);
1544 self.sources.extend(other.sources);
1545 }
1546}
1547
1548#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1550pub struct OutputContracts(pub Contracts);
1551
1552impl OutputContracts {
1553 pub fn into_contracts(self) -> impl Iterator<Item = (String, Contract)> {
1555 self.0.into_values().flatten()
1556 }
1557
1558 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1560 self.0.values().flatten()
1561 }
1562
1563 pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1565 self.contracts_iter().find_map(|(name, contract)| {
1566 (name == contract_name).then(|| CompactContractRef::from(contract))
1567 })
1568 }
1569
1570 pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1572 self.0.values_mut().find_map(|c| c.remove(contract_name))
1573 }
1574}
1575
1576#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1577pub struct UserDoc {
1578 #[serde(default, skip_serializing_if = "Option::is_none")]
1579 pub version: Option<u32>,
1580 #[serde(default, skip_serializing_if = "Option::is_none")]
1581 pub kind: Option<String>,
1582 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1583 pub methods: BTreeMap<String, UserDocNotice>,
1584 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1585 pub events: BTreeMap<String, UserDocNotice>,
1586 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1587 pub errors: BTreeMap<String, Vec<UserDocNotice>>,
1588 #[serde(default, skip_serializing_if = "Option::is_none")]
1589 pub notice: Option<String>,
1590}
1591
1592#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1593#[serde(untagged)]
1594pub enum UserDocNotice {
1595 Constructor(String),
1597 Notice { notice: String },
1598}
1599
1600#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1601pub struct DevDoc {
1602 #[serde(default, skip_serializing_if = "Option::is_none")]
1603 pub version: Option<u32>,
1604 #[serde(default, skip_serializing_if = "Option::is_none")]
1605 pub kind: Option<String>,
1606 #[serde(default, skip_serializing_if = "Option::is_none")]
1607 pub author: Option<String>,
1608 #[serde(default, skip_serializing_if = "Option::is_none")]
1609 pub details: Option<String>,
1610 #[serde(default, rename = "custom:experimental", skip_serializing_if = "Option::is_none")]
1611 pub custom_experimental: Option<String>,
1612 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1613 pub methods: BTreeMap<String, MethodDoc>,
1614 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1615 pub events: BTreeMap<String, EventDoc>,
1616 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1617 pub errors: BTreeMap<String, Vec<ErrorDoc>>,
1618 #[serde(default, skip_serializing_if = "Option::is_none")]
1619 pub title: Option<String>,
1620}
1621
1622#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1623pub struct MethodDoc {
1624 #[serde(default, skip_serializing_if = "Option::is_none")]
1625 pub details: Option<String>,
1626 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1627 pub params: BTreeMap<String, String>,
1628 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1629 pub returns: BTreeMap<String, String>,
1630}
1631
1632#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1633pub struct EventDoc {
1634 #[serde(default, skip_serializing_if = "Option::is_none")]
1635 pub details: Option<String>,
1636 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1637 pub params: BTreeMap<String, String>,
1638}
1639
1640#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1641pub struct ErrorDoc {
1642 #[serde(default, skip_serializing_if = "Option::is_none")]
1643 pub details: Option<String>,
1644 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1645 pub params: BTreeMap<String, String>,
1646}
1647
1648#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1649#[serde(rename_all = "camelCase")]
1650pub struct Evm {
1651 #[serde(default, skip_serializing_if = "Option::is_none")]
1652 pub assembly: Option<String>,
1653 #[serde(default, skip_serializing_if = "Option::is_none")]
1654 pub legacy_assembly: Option<serde_json::Value>,
1655 pub bytecode: Option<Bytecode>,
1656 #[serde(default, skip_serializing_if = "Option::is_none")]
1657 pub deployed_bytecode: Option<DeployedBytecode>,
1658 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1660 pub method_identifiers: BTreeMap<String, String>,
1661 #[serde(default, skip_serializing_if = "Option::is_none")]
1663 pub gas_estimates: Option<GasEstimates>,
1664}
1665
1666impl Evm {
1667 pub(crate) fn into_compact(self) -> CompactEvm {
1670 let Self {
1671 assembly,
1672 legacy_assembly,
1673 bytecode,
1674 deployed_bytecode,
1675 method_identifiers,
1676 gas_estimates,
1677 } = self;
1678
1679 let (bytecode, deployed_bytecode) = match (bytecode, deployed_bytecode) {
1680 (Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())),
1681 (None, Some(dbcode)) => (None, Some(dbcode.into())),
1682 (Some(bcode), None) => (Some(bcode.into()), None),
1683 (None, None) => (None, None),
1684 };
1685
1686 CompactEvm {
1687 assembly,
1688 legacy_assembly,
1689 bytecode,
1690 deployed_bytecode,
1691 method_identifiers,
1692 gas_estimates,
1693 }
1694 }
1695}
1696
1697#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1698#[serde(rename_all = "camelCase")]
1699pub(crate) struct CompactEvm {
1700 #[serde(default, skip_serializing_if = "Option::is_none")]
1701 pub assembly: Option<String>,
1702 #[serde(default, skip_serializing_if = "Option::is_none")]
1703 pub legacy_assembly: Option<serde_json::Value>,
1704 pub bytecode: Option<CompactBytecode>,
1705 #[serde(default, skip_serializing_if = "Option::is_none")]
1706 pub deployed_bytecode: Option<CompactDeployedBytecode>,
1707 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1709 pub method_identifiers: BTreeMap<String, String>,
1710 #[serde(default, skip_serializing_if = "Option::is_none")]
1712 pub gas_estimates: Option<GasEstimates>,
1713}
1714
1715#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1716#[serde(rename_all = "camelCase")]
1717pub struct FunctionDebugData {
1718 pub entry_point: Option<u32>,
1719 pub id: Option<u32>,
1720 pub parameter_slots: Option<u32>,
1721 pub return_slots: Option<u32>,
1722}
1723
1724#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1725pub struct GeneratedSource {
1726 pub ast: serde_json::Value,
1727 pub contents: String,
1728 pub id: u32,
1729 pub language: String,
1730 pub name: String,
1731}
1732
1733#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1736pub struct Offsets {
1737 pub start: u32,
1738 pub length: u32,
1739}
1740
1741#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1742pub struct GasEstimates {
1743 pub creation: Creation,
1744 #[serde(default)]
1745 pub external: BTreeMap<String, String>,
1746 #[serde(default)]
1747 pub internal: BTreeMap<String, String>,
1748}
1749
1750#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1751#[serde(rename_all = "camelCase")]
1752pub struct Creation {
1753 pub code_deposit_cost: String,
1754 pub execution_cost: String,
1755 pub total_cost: String,
1756}
1757
1758#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1759pub struct Ewasm {
1760 #[serde(default, skip_serializing_if = "Option::is_none")]
1761 pub wast: Option<String>,
1762 pub wasm: String,
1763}
1764
1765#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1767pub struct StorageLayout {
1768 pub storage: Vec<Storage>,
1769 #[serde(default, deserialize_with = "serde_helpers::default_for_null")]
1770 pub types: BTreeMap<String, StorageType>,
1771}
1772
1773impl StorageLayout {
1774 fn is_empty(&self) -> bool {
1775 self.storage.is_empty() && self.types.is_empty()
1776 }
1777}
1778
1779#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1780pub struct Storage {
1781 #[serde(rename = "astId")]
1782 pub ast_id: u64,
1783 pub contract: String,
1784 pub label: String,
1785 pub offset: i64,
1786 pub slot: String,
1787 #[serde(rename = "type")]
1788 pub storage_type: String,
1789}
1790
1791#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1792pub struct StorageType {
1793 pub encoding: String,
1794 #[serde(default, skip_serializing_if = "Option::is_none")]
1795 pub key: Option<String>,
1796 pub label: String,
1797 #[serde(rename = "numberOfBytes")]
1798 pub number_of_bytes: String,
1799 #[serde(default, skip_serializing_if = "Option::is_none")]
1800 pub value: Option<String>,
1801 #[serde(flatten)]
1803 pub other: BTreeMap<String, serde_json::Value>,
1804}
1805
1806#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1807pub struct SourceFile {
1808 pub id: u32,
1809 #[serde(default, with = "serde_helpers::empty_json_object_opt")]
1810 pub ast: Option<Ast>,
1811}
1812
1813impl SourceFile {
1814 pub fn contains_contract_definition(&self) -> bool {
1817 self.ast.as_ref().is_some_and(|ast| {
1818 ast.nodes.iter().any(|node| matches!(node.node_type, NodeType::ContractDefinition))
1819 })
1820 }
1821}
1822
1823#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1825pub struct SourceFiles(pub BTreeMap<PathBuf, SourceFile>);
1826
1827impl SourceFiles {
1828 pub fn into_ids(self) -> impl Iterator<Item = (u32, PathBuf)> {
1830 self.0.into_iter().map(|(k, v)| (v.id, k))
1831 }
1832
1833 pub fn into_paths(self) -> impl Iterator<Item = (PathBuf, u32)> {
1835 self.0.into_iter().map(|(k, v)| (k, v.id))
1836 }
1837}
1838
1839#[cfg(test)]
1840mod tests {
1841 use super::*;
1842 use alloy_primitives::Address;
1843 use similar_asserts::assert_eq;
1844 use std::fs;
1845
1846 #[test]
1847 fn can_link_bytecode() {
1848 #[derive(Serialize, Deserialize)]
1851 struct Mockject {
1852 object: BytecodeObject,
1853 }
1854 fn parse_bytecode(bytecode: &str) -> BytecodeObject {
1855 let object: Mockject =
1856 serde_json::from_value(serde_json::json!({ "object": bytecode })).unwrap();
1857 object.object
1858 }
1859
1860 let bytecode = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1861
1862 let mut object = parse_bytecode(bytecode);
1863 assert!(object.is_unlinked());
1864 assert!(object.contains_placeholder("lib2.sol", "L"));
1865 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1866 assert!(object.link("lib2.sol", "L", Address::random()).resolve().is_some());
1867 assert!(!object.is_unlinked());
1868
1869 let mut code = Bytecode {
1870 function_debug_data: Default::default(),
1871 object: parse_bytecode(bytecode),
1872 opcodes: None,
1873 source_map: None,
1874 generated_sources: vec![],
1875 link_references: BTreeMap::from([(
1876 "lib2.sol".to_string(),
1877 BTreeMap::from([("L".to_string(), vec![])]),
1878 )]),
1879 };
1880
1881 assert!(!code.link("lib2.sol", "Y", Address::random()));
1882 assert!(code.link("lib2.sol", "L", Address::random()));
1883 assert!(code.link("lib2.sol", "L", Address::random()));
1884
1885 let hashed_placeholder = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__$cb901161e812ceb78cfe30ca65050c4337$__6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1886 let mut object = parse_bytecode(hashed_placeholder);
1887 assert!(object.is_unlinked());
1888 assert!(object.contains_placeholder("lib2.sol", "L"));
1889 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1890 assert!(object.link("lib2.sol", "L", Address::default()).resolve().is_some());
1891 assert!(!object.is_unlinked());
1892 }
1893
1894 #[test]
1895 fn can_parse_compiler_output() {
1896 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/out");
1897
1898 for path in fs::read_dir(dir).unwrap() {
1899 let path = path.unwrap().path();
1900 let compiler_output = fs::read_to_string(&path).unwrap();
1901 serde_json::from_str::<CompilerOutput>(&compiler_output).unwrap_or_else(|err| {
1902 panic!("Failed to read compiler output of {} {}", path.display(), err)
1903 });
1904 }
1905 }
1906
1907 #[test]
1908 fn can_parse_compiler_input() {
1909 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1910
1911 for path in fs::read_dir(dir).unwrap() {
1912 let path = path.unwrap().path();
1913 let compiler_input = fs::read_to_string(&path).unwrap();
1914 serde_json::from_str::<SolcInput>(&compiler_input).unwrap_or_else(|err| {
1915 panic!("Failed to read compiler input of {} {}", path.display(), err)
1916 });
1917 }
1918 }
1919
1920 #[test]
1921 fn can_parse_standard_json_compiler_input() {
1922 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1923
1924 for path in fs::read_dir(dir).unwrap() {
1925 let path = path.unwrap().path();
1926 let compiler_input = fs::read_to_string(&path).unwrap();
1927 let val = serde_json::from_str::<StandardJsonCompilerInput>(&compiler_input)
1928 .unwrap_or_else(|err| {
1929 panic!("Failed to read compiler output of {} {}", path.display(), err)
1930 });
1931
1932 let pretty = serde_json::to_string_pretty(&val).unwrap();
1933 serde_json::from_str::<SolcInput>(&pretty).unwrap_or_else(|err| {
1934 panic!("Failed to read converted compiler input of {} {}", path.display(), err)
1935 });
1936 }
1937 }
1938
1939 #[test]
1940 fn test_evm_version_default() {
1941 for &(solc_version, expected) in &[
1942 ("0.4.20", None),
1944 ("0.4.21", Some(EvmVersion::Byzantium)),
1946 ("0.4.22", Some(EvmVersion::Byzantium)),
1948 ("0.5.5", Some(EvmVersion::Petersburg)),
1950 ("0.5.14", Some(EvmVersion::Istanbul)),
1952 ("0.8.5", Some(EvmVersion::Berlin)),
1954 ("0.8.7", Some(EvmVersion::London)),
1956 ("0.8.18", Some(EvmVersion::Paris)),
1958 ("0.8.20", Some(EvmVersion::Shanghai)),
1960 ("0.8.24", Some(EvmVersion::Shanghai)),
1962 ("0.8.25", Some(EvmVersion::Cancun)),
1963 ] {
1964 let version = Version::from_str(solc_version).unwrap();
1965 assert_eq!(
1966 EvmVersion::default_version_solc(&version),
1967 expected,
1968 "({version}, {expected:?})"
1969 )
1970 }
1971 }
1972
1973 #[test]
1974 fn test_evm_version_normalization() {
1975 for &(solc_version, evm_version, expected) in &[
1976 ("0.4.20", EvmVersion::Homestead, None),
1978 ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1980 ("0.4.21", EvmVersion::Constantinople, Some(EvmVersion::Byzantium)),
1981 ("0.4.21", EvmVersion::London, Some(EvmVersion::Byzantium)),
1982 ("0.4.22", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1984 ("0.4.22", EvmVersion::Constantinople, Some(EvmVersion::Constantinople)),
1985 ("0.4.22", EvmVersion::London, Some(EvmVersion::Constantinople)),
1986 ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1988 ("0.5.5", EvmVersion::Petersburg, Some(EvmVersion::Petersburg)),
1989 ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)),
1990 ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1992 ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)),
1993 ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)),
1994 ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1996 ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)),
1997 ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)),
1998 ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2000 ("0.8.7", EvmVersion::London, Some(EvmVersion::London)),
2001 ("0.8.7", EvmVersion::Paris, Some(EvmVersion::London)),
2002 ("0.8.18", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2004 ("0.8.18", EvmVersion::Paris, Some(EvmVersion::Paris)),
2005 ("0.8.18", EvmVersion::Shanghai, Some(EvmVersion::Paris)),
2006 ("0.8.20", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2008 ("0.8.20", EvmVersion::Paris, Some(EvmVersion::Paris)),
2009 ("0.8.20", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2010 ("0.8.20", EvmVersion::Cancun, Some(EvmVersion::Shanghai)),
2011 ("0.8.24", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2013 ("0.8.24", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2014 ("0.8.24", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2015 ("0.8.26", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2017 ("0.8.26", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2018 ("0.8.26", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2019 ("0.8.26", EvmVersion::Prague, Some(EvmVersion::Cancun)),
2020 ("0.8.27", EvmVersion::Prague, Some(EvmVersion::Prague)),
2021 ("0.8.29", EvmVersion::Osaka, Some(EvmVersion::Osaka)),
2022 ] {
2023 let version = Version::from_str(solc_version).unwrap();
2024 assert_eq!(
2025 evm_version.normalize_version_solc(&version),
2026 expected,
2027 "({version}, {evm_version:?})"
2028 )
2029 }
2030 }
2031
2032 #[test]
2033 fn can_sanitize_byte_code_hash() {
2034 let settings = Settings { metadata: Some(BytecodeHash::Ipfs.into()), ..Default::default() };
2035
2036 let input =
2037 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2038
2039 let i = input.clone().sanitized(&Version::new(0, 6, 0));
2040 assert_eq!(i.settings.metadata.unwrap().bytecode_hash, Some(BytecodeHash::Ipfs));
2041
2042 let i = input.sanitized(&Version::new(0, 5, 17));
2043 assert!(i.settings.metadata.unwrap().bytecode_hash.is_none());
2044 }
2045
2046 #[test]
2047 fn can_sanitize_cbor_metadata() {
2048 let settings = Settings {
2049 metadata: Some(SettingsMetadata::new(BytecodeHash::Ipfs, true)),
2050 ..Default::default()
2051 };
2052
2053 let input =
2054 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2055
2056 let i = input.clone().sanitized(&Version::new(0, 8, 18));
2057 assert_eq!(i.settings.metadata.unwrap().cbor_metadata, Some(true));
2058
2059 let i = input.sanitized(&Version::new(0, 8, 0));
2060 assert!(i.settings.metadata.unwrap().cbor_metadata.is_none());
2061 }
2062
2063 #[test]
2064 fn can_parse_libraries() {
2065 let libraries = ["./src/lib/LibraryContract.sol:Library:0xaddress".to_string()];
2066
2067 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2068
2069 assert_eq!(
2070 libs,
2071 BTreeMap::from([(
2072 PathBuf::from("./src/lib/LibraryContract.sol"),
2073 BTreeMap::from([("Library".to_string(), "0xaddress".to_string())])
2074 )])
2075 );
2076 }
2077
2078 #[test]
2079 fn can_strip_libraries_path_prefixes() {
2080 let libraries= [
2081 "/global/root/src/FileInSrc.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2082 "src/deep/DeepFileInSrc.sol:ChainlinkTWAP:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2083 "/global/GlobalFile.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2084 "/global/root/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2085 "test/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2086 ];
2087
2088 let libs = Libraries::parse(&libraries[..])
2089 .unwrap()
2090 .with_stripped_file_prefixes("/global/root".as_ref())
2091 .libs;
2092
2093 assert_eq!(
2094 libs,
2095 BTreeMap::from([
2096 (
2097 PathBuf::from("/global/GlobalFile.sol"),
2098 BTreeMap::from([(
2099 "Math".to_string(),
2100 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2101 )])
2102 ),
2103 (
2104 PathBuf::from("src/FileInSrc.sol"),
2105 BTreeMap::from([(
2106 "Chainlink".to_string(),
2107 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2108 )])
2109 ),
2110 (
2111 PathBuf::from("src/deep/DeepFileInSrc.sol"),
2112 BTreeMap::from([(
2113 "ChainlinkTWAP".to_string(),
2114 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2115 )])
2116 ),
2117 (
2118 PathBuf::from("test/SizeAuctionDiscount.sol"),
2119 BTreeMap::from([(
2120 "Math".to_string(),
2121 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2122 )])
2123 ),
2124 (
2125 PathBuf::from("test/ChainlinkTWAP.t.sol"),
2126 BTreeMap::from([(
2127 "ChainlinkTWAP".to_string(),
2128 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2129 )])
2130 ),
2131 ])
2132 );
2133 }
2134
2135 #[test]
2136 fn can_parse_many_libraries() {
2137 let libraries= [
2138 "./src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2139 "./src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2140 "./src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2141 "./src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2142 "./src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2143 ];
2144
2145 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2146
2147 assert_eq!(
2148 libs,
2149 BTreeMap::from([
2150 (
2151 PathBuf::from("./src/SizeAuctionDiscount.sol"),
2152 BTreeMap::from([
2153 (
2154 "Chainlink".to_string(),
2155 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2156 ),
2157 (
2158 "Math".to_string(),
2159 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2160 )
2161 ])
2162 ),
2163 (
2164 PathBuf::from("./src/SizeAuction.sol"),
2165 BTreeMap::from([
2166 (
2167 "ChainlinkTWAP".to_string(),
2168 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2169 ),
2170 (
2171 "Math".to_string(),
2172 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2173 )
2174 ])
2175 ),
2176 (
2177 PathBuf::from("./src/test/ChainlinkTWAP.t.sol"),
2178 BTreeMap::from([(
2179 "ChainlinkTWAP".to_string(),
2180 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2181 )])
2182 ),
2183 ])
2184 );
2185 }
2186
2187 #[test]
2188 fn test_lossless_metadata() {
2189 #[derive(Debug, Serialize, Deserialize)]
2190 #[serde(rename_all = "camelCase")]
2191 pub struct Contract {
2192 #[serde(
2193 default,
2194 skip_serializing_if = "Option::is_none",
2195 with = "serde_helpers::json_string_opt"
2196 )]
2197 pub metadata: Option<LosslessMetadata>,
2198 }
2199
2200 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}"}"#;
2201
2202 let value: serde_json::Value = serde_json::from_str(s).unwrap();
2203 let c: Contract = serde_json::from_value(value).unwrap();
2204 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}");
2205
2206 let value = serde_json::to_string(&c).unwrap();
2207 assert_eq!(s, value);
2208 }
2209
2210 #[test]
2211 fn test_lossless_storage_layout() {
2212 let input = include_str!("../../../../test-data/foundryissue2462.json").trim();
2213 let layout: StorageLayout = serde_json::from_str(input).unwrap();
2214 assert_eq!(input, &serde_json::to_string(&layout).unwrap());
2215 }
2216
2217 #[test]
2219 fn can_parse_compiler_output_spells_0_6_12() {
2220 let path =
2221 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/0.6.12-with-libs.json");
2222 let content = fs::read_to_string(path).unwrap();
2223 let _output: CompilerOutput = serde_json::from_str(&content).unwrap();
2224 }
2225
2226 #[test]
2228 fn can_sanitize_optimizer_inliner() {
2229 let settings = Settings::default().with_via_ir_minimum_optimization();
2230
2231 let input =
2232 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2233
2234 let i = input.clone().sanitized(&Version::new(0, 8, 4));
2235 assert!(i.settings.optimizer.details.unwrap().inliner.is_none());
2236
2237 let i = input.sanitized(&Version::new(0, 8, 5));
2238 assert_eq!(i.settings.optimizer.details.unwrap().inliner, Some(false));
2239 }
2240}