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")]
235 pub stop_after: Option<String>,
236 #[serde(default, skip_serializing_if = "Vec::is_empty")]
237 pub remappings: Vec<Remapping>,
238 #[serde(default)]
240 pub optimizer: Optimizer,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub model_checker: Option<ModelCheckerSettings>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub metadata: Option<SettingsMetadata>,
247 #[serde(default)]
252 pub output_selection: OutputSelection,
253 #[serde(
254 default,
255 with = "serde_helpers::display_from_str_opt",
256 skip_serializing_if = "Option::is_none"
257 )]
258 pub evm_version: Option<EvmVersion>,
259 #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
262 pub via_ir: Option<bool>,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub debug: Option<DebuggingSettings>,
265 #[serde(default)]
273 pub libraries: Libraries,
274}
275
276impl Settings {
277 pub fn new(output_selection: impl Into<OutputSelection>) -> Self {
279 Self { output_selection: output_selection.into(), ..Default::default() }
280 }
281
282 pub fn sanitized(mut self, version: &Version, language: SolcLanguage) -> Self {
284 self.sanitize(version, language);
285 self
286 }
287
288 pub fn sanitize(&mut self, version: &Version, language: SolcLanguage) {
290 if *version < Version::new(0, 6, 0) {
291 if let Some(meta) = &mut self.metadata {
292 meta.bytecode_hash = None;
295 }
296 self.debug = None;
298 }
299
300 if *version < Version::new(0, 7, 5) {
301 self.via_ir = None;
303 }
304
305 if *version < Version::new(0, 8, 5) {
306 if let Some(optimizer_details) = &mut self.optimizer.details {
308 optimizer_details.inliner = None;
309 }
310 }
311
312 if *version < Version::new(0, 8, 7) {
313 self.model_checker = None;
317 }
318
319 if *version < Version::new(0, 8, 10) {
320 if let Some(debug) = &mut self.debug {
321 debug.debug_info.clear();
324 }
325
326 if let Some(model_checker) = &mut self.model_checker {
327 model_checker.invariants = None;
329 }
330 }
331
332 if *version < Version::new(0, 8, 18) {
333 if let Some(meta) = &mut self.metadata {
335 meta.cbor_metadata = None;
336 }
337
338 if let Some(model_checker) = &mut self.model_checker {
339 if let Some(solvers) = &mut model_checker.solvers {
340 solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
342 }
343 }
344 }
345
346 if *version < Version::new(0, 8, 20) {
347 if let Some(model_checker) = &mut self.model_checker {
349 model_checker.show_proved_safe = None;
350 model_checker.show_unsupported = None;
351 }
352 }
353
354 if let Some(evm_version) = self.evm_version {
355 self.evm_version = evm_version.normalize_version_solc(version);
356 }
357
358 match language {
359 SolcLanguage::Solidity => {}
360 SolcLanguage::Yul => {
361 if !self.remappings.is_empty() {
362 warn!("omitting remappings supplied for the yul sources");
363 }
364 self.remappings = Vec::new();
365 }
366 }
367 }
368
369 pub fn push_all(&mut self, settings: impl IntoIterator<Item = ContractOutputSelection>) {
371 for value in settings {
372 self.push_output_selection(value)
373 }
374 }
375
376 #[must_use]
378 pub fn with_extra_output(
379 mut self,
380 settings: impl IntoIterator<Item = ContractOutputSelection>,
381 ) -> Self {
382 for value in settings {
383 self.push_output_selection(value)
384 }
385 self
386 }
387
388 pub fn push_output_selection(&mut self, value: impl ToString) {
396 self.push_contract_output_selection("*", value)
397 }
398
399 pub fn push_contract_output_selection(
403 &mut self,
404 contracts: impl Into<String>,
405 value: impl ToString,
406 ) {
407 let value = value.to_string();
408 let values = self
409 .output_selection
410 .as_mut()
411 .entry("*".to_string())
412 .or_default()
413 .entry(contracts.into())
414 .or_default();
415 if !values.contains(&value) {
416 values.push(value)
417 }
418 }
419
420 pub fn set_output_selection(&mut self, values: impl IntoIterator<Item = impl ToString>) {
422 self.set_contract_output_selection("*", values)
423 }
424
425 pub fn set_contract_output_selection(
429 &mut self,
430 key: impl Into<String>,
431 values: impl IntoIterator<Item = impl ToString>,
432 ) {
433 self.output_selection
434 .as_mut()
435 .entry("*".to_string())
436 .or_default()
437 .insert(key.into(), values.into_iter().map(|s| s.to_string()).collect());
438 }
439
440 #[must_use]
442 pub fn set_via_ir(mut self, via_ir: bool) -> Self {
443 self.via_ir = Some(via_ir);
444 self
445 }
446
447 #[must_use]
449 pub fn with_via_ir(self) -> Self {
450 self.set_via_ir(true)
451 }
452
453 pub fn with_via_ir_minimum_optimization(mut self) -> Self {
460 self.via_ir = Some(true);
463 self.optimizer.details = Some(OptimizerDetails {
464 peephole: Some(false),
465 inliner: Some(false),
466 jumpdest_remover: Some(false),
467 order_literals: Some(false),
468 deduplicate: Some(false),
469 cse: Some(false),
470 constant_optimizer: Some(false),
471 yul: Some(true), yul_details: Some(YulDetails {
473 stack_allocation: Some(true),
474 optimizer_steps: Some("u".to_string()),
476 }),
477 simple_counter_for_loop_unchecked_increment: None,
479 });
480 self
481 }
482
483 #[must_use]
485 pub fn with_ast(mut self) -> Self {
486 let output = self.output_selection.as_mut().entry("*".to_string()).or_default();
487 output.insert(String::new(), vec!["ast".to_string()]);
488 self
489 }
490
491 pub fn strip_prefix(&mut self, base: &Path) {
492 self.remappings.iter_mut().for_each(|r| {
493 r.strip_prefix(base);
494 });
495
496 self.libraries.libs = std::mem::take(&mut self.libraries.libs)
497 .into_iter()
498 .map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs))
499 .collect();
500
501 self.output_selection = OutputSelection(
502 std::mem::take(&mut self.output_selection.0)
503 .into_iter()
504 .map(|(file, selection)| {
505 (
506 Path::new(&file)
507 .strip_prefix(base)
508 .map(|p| p.display().to_string())
509 .unwrap_or(file),
510 selection,
511 )
512 })
513 .collect(),
514 );
515
516 if let Some(mut model_checker) = self.model_checker.take() {
517 model_checker.contracts = model_checker
518 .contracts
519 .into_iter()
520 .map(|(path, contracts)| {
521 (
522 Path::new(&path)
523 .strip_prefix(base)
524 .map(|p| p.display().to_string())
525 .unwrap_or(path),
526 contracts,
527 )
528 })
529 .collect();
530 self.model_checker = Some(model_checker);
531 }
532 }
533
534 pub fn with_base_path(mut self, base: &Path) -> Self {
536 self.strip_prefix(base);
537 self
538 }
539}
540
541impl Default for Settings {
542 fn default() -> Self {
543 Self {
544 stop_after: None,
545 optimizer: Default::default(),
546 metadata: None,
547 output_selection: OutputSelection::default_output_selection(),
548 evm_version: Some(EvmVersion::default()),
549 via_ir: None,
550 debug: None,
551 libraries: Default::default(),
552 remappings: Default::default(),
553 model_checker: None,
554 }
555 .with_ast()
556 }
557}
558
559#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
561#[serde(transparent)]
562pub struct Libraries {
563 pub libs: BTreeMap<PathBuf, BTreeMap<String, String>>,
565}
566
567impl Libraries {
570 pub fn parse(libs: &[String]) -> Result<Self, SolcError> {
584 let mut libraries = BTreeMap::default();
585 for lib in libs {
586 let mut items = lib.split(':');
587 let file = items.next().ok_or_else(|| {
588 SolcError::msg(format!("failed to parse path to library file: {lib}"))
589 })?;
590 let lib = items
591 .next()
592 .ok_or_else(|| SolcError::msg(format!("failed to parse library name: {lib}")))?;
593 let addr = items
594 .next()
595 .ok_or_else(|| SolcError::msg(format!("failed to parse library address: {lib}")))?;
596 if items.next().is_some() {
597 return Err(SolcError::msg(format!(
598 "failed to parse, too many arguments passed: {lib}"
599 )));
600 }
601 libraries
602 .entry(file.into())
603 .or_insert_with(BTreeMap::default)
604 .insert(lib.to_string(), addr.to_string());
605 }
606 Ok(Self { libs: libraries })
607 }
608
609 pub fn is_empty(&self) -> bool {
610 self.libs.is_empty()
611 }
612
613 pub fn len(&self) -> usize {
614 self.libs.len()
615 }
616
617 pub fn apply<F: FnOnce(Self) -> Self>(self, f: F) -> Self {
619 f(self)
620 }
621
622 pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
625 self.libs = self
626 .libs
627 .into_iter()
628 .map(|(f, l)| (f.strip_prefix(base).unwrap_or(&f).to_path_buf(), l))
629 .collect();
630 self
631 }
632
633 pub fn slash_paths(&mut self) {
635 #[cfg(windows)]
636 {
637 use path_slash::PathBufExt;
638
639 self.libs = std::mem::take(&mut self.libs)
640 .into_iter()
641 .map(|(path, libs)| (PathBuf::from(path.to_slash_lossy().as_ref()), libs))
642 .collect()
643 }
644 }
645}
646
647impl From<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
648 fn from(libs: BTreeMap<PathBuf, BTreeMap<String, String>>) -> Self {
649 Self { libs }
650 }
651}
652
653impl AsRef<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
654 fn as_ref(&self) -> &BTreeMap<PathBuf, BTreeMap<String, String>> {
655 &self.libs
656 }
657}
658
659impl AsMut<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
660 fn as_mut(&mut self) -> &mut BTreeMap<PathBuf, BTreeMap<String, String>> {
661 &mut self.libs
662 }
663}
664
665#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
666pub struct Optimizer {
667 #[serde(default, skip_serializing_if = "Option::is_none")]
668 pub enabled: Option<bool>,
669 #[serde(default, skip_serializing_if = "Option::is_none")]
670 pub runs: Option<usize>,
671 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub details: Option<OptimizerDetails>,
676}
677
678impl Optimizer {
679 pub fn runs(&mut self, runs: usize) {
680 self.runs = Some(runs);
681 }
682
683 pub fn disable(&mut self) {
684 self.enabled.take();
685 }
686
687 pub fn enable(&mut self) {
688 self.enabled = Some(true)
689 }
690}
691
692impl Default for Optimizer {
693 fn default() -> Self {
694 Self { enabled: Some(false), runs: Some(200), details: None }
695 }
696}
697
698#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
699#[serde(rename_all = "camelCase")]
700pub struct OptimizerDetails {
701 #[serde(default, skip_serializing_if = "Option::is_none")]
704 pub peephole: Option<bool>,
705 #[serde(default, skip_serializing_if = "Option::is_none")]
708 pub inliner: Option<bool>,
709 #[serde(default, skip_serializing_if = "Option::is_none")]
712 pub jumpdest_remover: Option<bool>,
713 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub order_literals: Option<bool>,
716 #[serde(default, skip_serializing_if = "Option::is_none")]
718 pub deduplicate: Option<bool>,
719 #[serde(default, skip_serializing_if = "Option::is_none")]
722 pub cse: Option<bool>,
723 #[serde(default, skip_serializing_if = "Option::is_none")]
725 pub constant_optimizer: Option<bool>,
726 #[serde(default, skip_serializing_if = "Option::is_none")]
732 pub yul: Option<bool>,
733 #[serde(default, skip_serializing_if = "Option::is_none")]
735 pub yul_details: Option<YulDetails>,
736 #[serde(default, skip_serializing_if = "Option::is_none")]
739 pub simple_counter_for_loop_unchecked_increment: Option<bool>,
740}
741
742impl OptimizerDetails {
745 pub fn is_empty(&self) -> bool {
747 self.peephole.is_none()
748 && self.inliner.is_none()
749 && self.jumpdest_remover.is_none()
750 && self.order_literals.is_none()
751 && self.deduplicate.is_none()
752 && self.cse.is_none()
753 && self.constant_optimizer.is_none()
754 && self.yul.is_none()
755 && self.yul_details.as_ref().map(|yul| yul.is_empty()).unwrap_or(true)
756 && self.simple_counter_for_loop_unchecked_increment.is_none()
757 }
758}
759
760#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
761#[serde(rename_all = "camelCase")]
762pub struct YulDetails {
763 #[serde(default, skip_serializing_if = "Option::is_none")]
766 pub stack_allocation: Option<bool>,
767 #[serde(default, skip_serializing_if = "Option::is_none")]
770 pub optimizer_steps: Option<String>,
771}
772
773impl YulDetails {
776 pub fn is_empty(&self) -> bool {
778 self.stack_allocation.is_none() && self.optimizer_steps.is_none()
779 }
780}
781
782#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
795pub enum EvmVersion {
796 Homestead,
797 TangerineWhistle,
798 SpuriousDragon,
799 Byzantium,
800 Constantinople,
801 Petersburg,
802 Istanbul,
803 Berlin,
804 London,
805 Paris,
806 Shanghai,
807 Cancun,
808 #[default]
809 Prague,
810 Osaka,
811}
812
813impl EvmVersion {
814 pub fn default_version_solc(version: &Version) -> Option<Self> {
816 let default = Self::default().normalize_version_solc(version)?;
818
819 match default {
821 Self::Constantinople => {
822 Some(Self::Byzantium)
825 }
826 Self::Cancun if *version == Version::new(0, 8, 24) => {
827 Some(Self::Shanghai)
833 }
834 Self::Prague if *version == Version::new(0, 8, 27) => {
835 Some(Self::Cancun)
837 }
838 _ => Some(default),
839 }
840 }
841
842 pub fn normalize_version_solc(self, version: &Version) -> Option<Self> {
844 if *version >= BYZANTIUM_SOLC {
846 let normalized = if *version >= OSAKA_SOLC {
849 self
850 } else if self >= Self::Prague && *version >= PRAGUE_SOLC {
851 Self::Prague
852 } else if self >= Self::Cancun && *version >= CANCUN_SOLC {
853 Self::Cancun
854 } else if self >= Self::Shanghai && *version >= SHANGHAI_SOLC {
855 Self::Shanghai
856 } else if self >= Self::Paris && *version >= PARIS_SOLC {
857 Self::Paris
858 } else if self >= Self::London && *version >= LONDON_SOLC {
859 Self::London
860 } else if self >= Self::Berlin && *version >= BERLIN_SOLC {
861 Self::Berlin
862 } else if self >= Self::Istanbul && *version >= ISTANBUL_SOLC {
863 Self::Istanbul
864 } else if self >= Self::Petersburg && *version >= PETERSBURG_SOLC {
865 Self::Petersburg
866 } else if self >= Self::Constantinople && *version >= CONSTANTINOPLE_SOLC {
867 Self::Constantinople
868 } else if self >= Self::Byzantium {
869 Self::Byzantium
870 } else {
871 self
872 };
873 Some(normalized)
874 } else {
875 None
876 }
877 }
878
879 pub const fn as_str(&self) -> &'static str {
881 match self {
882 Self::Homestead => "homestead",
883 Self::TangerineWhistle => "tangerineWhistle",
884 Self::SpuriousDragon => "spuriousDragon",
885 Self::Byzantium => "byzantium",
886 Self::Constantinople => "constantinople",
887 Self::Petersburg => "petersburg",
888 Self::Istanbul => "istanbul",
889 Self::Berlin => "berlin",
890 Self::London => "london",
891 Self::Paris => "paris",
892 Self::Shanghai => "shanghai",
893 Self::Cancun => "cancun",
894 Self::Prague => "prague",
895 Self::Osaka => "osaka",
896 }
897 }
898
899 pub fn supports_returndata(&self) -> bool {
901 *self >= Self::Byzantium
902 }
903
904 pub fn has_static_call(&self) -> bool {
905 *self >= Self::Byzantium
906 }
907
908 pub fn has_bitwise_shifting(&self) -> bool {
909 *self >= Self::Constantinople
910 }
911
912 pub fn has_create2(&self) -> bool {
913 *self >= Self::Constantinople
914 }
915
916 pub fn has_ext_code_hash(&self) -> bool {
917 *self >= Self::Constantinople
918 }
919
920 pub fn has_chain_id(&self) -> bool {
921 *self >= Self::Istanbul
922 }
923
924 pub fn has_self_balance(&self) -> bool {
925 *self >= Self::Istanbul
926 }
927
928 pub fn has_base_fee(&self) -> bool {
929 *self >= Self::London
930 }
931
932 pub fn has_prevrandao(&self) -> bool {
933 *self >= Self::Paris
934 }
935
936 pub fn has_push0(&self) -> bool {
937 *self >= Self::Shanghai
938 }
939}
940
941impl fmt::Display for EvmVersion {
942 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
943 f.write_str(self.as_str())
944 }
945}
946
947impl FromStr for EvmVersion {
948 type Err = String;
949
950 fn from_str(s: &str) -> Result<Self, Self::Err> {
951 match s {
952 "homestead" => Ok(Self::Homestead),
953 "tangerineWhistle" | "tangerinewhistle" => Ok(Self::TangerineWhistle),
954 "spuriousDragon" | "spuriousdragon" => Ok(Self::SpuriousDragon),
955 "byzantium" => Ok(Self::Byzantium),
956 "constantinople" => Ok(Self::Constantinople),
957 "petersburg" => Ok(Self::Petersburg),
958 "istanbul" => Ok(Self::Istanbul),
959 "berlin" => Ok(Self::Berlin),
960 "london" => Ok(Self::London),
961 "paris" => Ok(Self::Paris),
962 "shanghai" => Ok(Self::Shanghai),
963 "cancun" => Ok(Self::Cancun),
964 "prague" => Ok(Self::Prague),
965 "osaka" => Ok(Self::Osaka),
966 s => Err(format!("Unknown evm version: {s}")),
967 }
968 }
969}
970
971#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
973#[serde(rename_all = "camelCase")]
974pub struct DebuggingSettings {
975 #[serde(
976 default,
977 with = "serde_helpers::display_from_str_opt",
978 skip_serializing_if = "Option::is_none"
979 )]
980 pub revert_strings: Option<RevertStrings>,
981 #[serde(default, skip_serializing_if = "Vec::is_empty")]
993 pub debug_info: Vec<String>,
994}
995
996#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
998pub enum RevertStrings {
999 #[default]
1001 Default,
1002 Strip,
1005 Debug,
1008 VerboseDebug,
1011}
1012
1013impl fmt::Display for RevertStrings {
1014 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1015 let string = match self {
1016 Self::Default => "default",
1017 Self::Strip => "strip",
1018 Self::Debug => "debug",
1019 Self::VerboseDebug => "verboseDebug",
1020 };
1021 write!(f, "{string}")
1022 }
1023}
1024
1025impl FromStr for RevertStrings {
1026 type Err = String;
1027
1028 fn from_str(s: &str) -> Result<Self, Self::Err> {
1029 match s {
1030 "default" => Ok(Self::Default),
1031 "strip" => Ok(Self::Strip),
1032 "debug" => Ok(Self::Debug),
1033 "verboseDebug" | "verbosedebug" => Ok(Self::VerboseDebug),
1034 s => Err(format!("Unknown revert string mode: {s}")),
1035 }
1036 }
1037}
1038
1039#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1040pub struct SettingsMetadata {
1041 #[serde(default, rename = "useLiteralContent", skip_serializing_if = "Option::is_none")]
1043 pub use_literal_content: Option<bool>,
1044 #[serde(
1049 default,
1050 rename = "bytecodeHash",
1051 skip_serializing_if = "Option::is_none",
1052 with = "serde_helpers::display_from_str_opt"
1053 )]
1054 pub bytecode_hash: Option<BytecodeHash>,
1055 #[serde(default, rename = "appendCBOR", skip_serializing_if = "Option::is_none")]
1056 pub cbor_metadata: Option<bool>,
1057}
1058
1059impl SettingsMetadata {
1060 pub fn new(hash: BytecodeHash, cbor: bool) -> Self {
1061 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: Some(cbor) }
1062 }
1063}
1064
1065impl From<BytecodeHash> for SettingsMetadata {
1066 fn from(hash: BytecodeHash) -> Self {
1067 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: None }
1068 }
1069}
1070
1071#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1075pub enum BytecodeHash {
1076 #[default]
1077 Ipfs,
1078 None,
1079 Bzzr1,
1080}
1081
1082impl FromStr for BytecodeHash {
1083 type Err = String;
1084
1085 fn from_str(s: &str) -> Result<Self, Self::Err> {
1086 match s {
1087 "none" => Ok(Self::None),
1088 "ipfs" => Ok(Self::Ipfs),
1089 "bzzr1" => Ok(Self::Bzzr1),
1090 s => Err(format!("Unknown bytecode hash: {s}")),
1091 }
1092 }
1093}
1094
1095impl fmt::Display for BytecodeHash {
1096 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1097 let s = match self {
1098 Self::Ipfs => "ipfs",
1099 Self::None => "none",
1100 Self::Bzzr1 => "bzzr1",
1101 };
1102 f.write_str(s)
1103 }
1104}
1105
1106#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1108pub struct Metadata {
1109 pub compiler: Compiler,
1110 pub language: String,
1111 pub output: Output,
1112 pub settings: MetadataSettings,
1113 pub sources: MetadataSources,
1114 pub version: i64,
1115}
1116
1117#[derive(Clone, Debug, PartialEq, Eq)]
1120pub struct LosslessMetadata {
1121 pub raw_metadata: String,
1123 pub metadata: Metadata,
1125}
1126
1127impl LosslessMetadata {
1130 pub fn raw_json(&self) -> serde_json::Result<serde_json::Value> {
1132 serde_json::from_str(&self.raw_metadata)
1133 }
1134}
1135
1136impl Serialize for LosslessMetadata {
1137 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1138 where
1139 S: Serializer,
1140 {
1141 serializer.serialize_str(&self.raw_metadata)
1142 }
1143}
1144
1145impl<'de> Deserialize<'de> for LosslessMetadata {
1146 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1147 where
1148 D: Deserializer<'de>,
1149 {
1150 struct LosslessMetadataVisitor;
1151
1152 impl Visitor<'_> for LosslessMetadataVisitor {
1153 type Value = LosslessMetadata;
1154
1155 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1156 write!(formatter, "metadata string")
1157 }
1158
1159 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1160 where
1161 E: serde::de::Error,
1162 {
1163 let metadata = serde_json::from_str(value).map_err(serde::de::Error::custom)?;
1164 let raw_metadata = value.to_string();
1165 Ok(LosslessMetadata { raw_metadata, metadata })
1166 }
1167 }
1168 deserializer.deserialize_str(LosslessMetadataVisitor)
1169 }
1170}
1171
1172#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1174pub struct MetadataSettings {
1175 #[serde(default)]
1176 pub remappings: Vec<Remapping>,
1177 pub optimizer: Optimizer,
1178 #[serde(default, skip_serializing_if = "Option::is_none")]
1179 pub metadata: Option<SettingsMetadata>,
1180 #[serde(default, rename = "compilationTarget")]
1183 pub compilation_target: BTreeMap<String, String>,
1184 #[serde(
1186 default,
1187 rename = "evmVersion",
1188 with = "serde_helpers::display_from_str_opt",
1189 skip_serializing_if = "Option::is_none"
1190 )]
1191 pub evm_version: Option<EvmVersion>,
1192 #[serde(default)]
1197 pub libraries: BTreeMap<String, String>,
1198 #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
1201 pub via_ir: Option<bool>,
1202}
1203
1204#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1206pub struct MetadataSources {
1207 #[serde(flatten)]
1208 pub inner: BTreeMap<String, MetadataSource>,
1209}
1210
1211#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1212pub struct MetadataSource {
1213 pub keccak256: String,
1215 #[serde(default)]
1219 pub urls: Vec<String>,
1220 #[serde(default, skip_serializing_if = "Option::is_none")]
1222 pub content: Option<String>,
1223 pub license: Option<String>,
1225}
1226
1227#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1229#[serde(rename_all = "camelCase")]
1230pub struct ModelCheckerSettings {
1231 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1232 pub contracts: BTreeMap<String, Vec<String>>,
1233 #[serde(
1234 default,
1235 with = "serde_helpers::display_from_str_opt",
1236 skip_serializing_if = "Option::is_none"
1237 )]
1238 pub engine: Option<ModelCheckerEngine>,
1239 #[serde(skip_serializing_if = "Option::is_none")]
1240 pub timeout: Option<u32>,
1241 #[serde(skip_serializing_if = "Option::is_none")]
1242 pub targets: Option<Vec<ModelCheckerTarget>>,
1243 #[serde(skip_serializing_if = "Option::is_none")]
1244 pub invariants: Option<Vec<ModelCheckerInvariant>>,
1245 #[serde(skip_serializing_if = "Option::is_none")]
1246 pub show_unproved: Option<bool>,
1247 #[serde(skip_serializing_if = "Option::is_none")]
1248 pub div_mod_with_slacks: Option<bool>,
1249 #[serde(skip_serializing_if = "Option::is_none")]
1250 pub solvers: Option<Vec<ModelCheckerSolver>>,
1251 #[serde(skip_serializing_if = "Option::is_none")]
1252 pub show_unsupported: Option<bool>,
1253 #[serde(skip_serializing_if = "Option::is_none")]
1254 pub show_proved_safe: Option<bool>,
1255}
1256
1257#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1259pub enum ModelCheckerEngine {
1260 #[default]
1261 Default,
1262 All,
1263 BMC,
1264 CHC,
1265}
1266
1267impl fmt::Display for ModelCheckerEngine {
1268 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1269 let string = match self {
1270 Self::Default => "none",
1271 Self::All => "all",
1272 Self::BMC => "bmc",
1273 Self::CHC => "chc",
1274 };
1275 write!(f, "{string}")
1276 }
1277}
1278
1279impl FromStr for ModelCheckerEngine {
1280 type Err = String;
1281
1282 fn from_str(s: &str) -> Result<Self, Self::Err> {
1283 match s {
1284 "none" => Ok(Self::Default),
1285 "all" => Ok(Self::All),
1286 "bmc" => Ok(Self::BMC),
1287 "chc" => Ok(Self::CHC),
1288 s => Err(format!("Unknown model checker engine: {s}")),
1289 }
1290 }
1291}
1292
1293#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1295#[serde(rename_all = "camelCase")]
1296pub enum ModelCheckerTarget {
1297 Assert,
1298 Underflow,
1299 Overflow,
1300 DivByZero,
1301 ConstantCondition,
1302 PopEmptyArray,
1303 OutOfBounds,
1304 Balance,
1305}
1306
1307impl fmt::Display for ModelCheckerTarget {
1308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1309 let string = match self {
1310 Self::Assert => "assert",
1311 Self::Underflow => "underflow",
1312 Self::Overflow => "overflow",
1313 Self::DivByZero => "divByZero",
1314 Self::ConstantCondition => "constantCondition",
1315 Self::PopEmptyArray => "popEmptyArray",
1316 Self::OutOfBounds => "outOfBounds",
1317 Self::Balance => "balance",
1318 };
1319 write!(f, "{string}")
1320 }
1321}
1322
1323impl FromStr for ModelCheckerTarget {
1324 type Err = String;
1325
1326 fn from_str(s: &str) -> Result<Self, Self::Err> {
1327 match s {
1328 "assert" => Ok(Self::Assert),
1329 "underflow" => Ok(Self::Underflow),
1330 "overflow" => Ok(Self::Overflow),
1331 "divByZero" => Ok(Self::DivByZero),
1332 "constantCondition" => Ok(Self::ConstantCondition),
1333 "popEmptyArray" => Ok(Self::PopEmptyArray),
1334 "outOfBounds" => Ok(Self::OutOfBounds),
1335 "balance" => Ok(Self::Balance),
1336 s => Err(format!("Unknown model checker target: {s}")),
1337 }
1338 }
1339}
1340
1341#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1343#[serde(rename_all = "camelCase")]
1344pub enum ModelCheckerInvariant {
1345 Contract,
1346 Reentrancy,
1347}
1348
1349impl fmt::Display for ModelCheckerInvariant {
1350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1351 let string = match self {
1352 Self::Contract => "contract",
1353 Self::Reentrancy => "reentrancy",
1354 };
1355 write!(f, "{string}")
1356 }
1357}
1358
1359impl FromStr for ModelCheckerInvariant {
1360 type Err = String;
1361
1362 fn from_str(s: &str) -> Result<Self, Self::Err> {
1363 match s {
1364 "contract" => Ok(Self::Contract),
1365 "reentrancy" => Ok(Self::Reentrancy),
1366 s => Err(format!("Unknown model checker invariant: {s}")),
1367 }
1368 }
1369}
1370
1371#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1373#[serde(rename_all = "camelCase")]
1374pub enum ModelCheckerSolver {
1375 Cvc4,
1376 Eld,
1377 Smtlib2,
1378 Z3,
1379}
1380
1381impl fmt::Display for ModelCheckerSolver {
1382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1383 let string = match self {
1384 Self::Cvc4 => "cvc4",
1385 Self::Eld => "eld",
1386 Self::Smtlib2 => "smtlib2",
1387 Self::Z3 => "z3",
1388 };
1389 write!(f, "{string}")
1390 }
1391}
1392
1393impl FromStr for ModelCheckerSolver {
1394 type Err = String;
1395
1396 fn from_str(s: &str) -> Result<Self, Self::Err> {
1397 match s {
1398 "cvc4" => Ok(Self::Cvc4),
1399 "eld" => Ok(Self::Cvc4),
1400 "smtlib2" => Ok(Self::Smtlib2),
1401 "z3" => Ok(Self::Z3),
1402 s => Err(format!("Unknown model checker invariant: {s}")),
1403 }
1404 }
1405}
1406
1407#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1408pub struct Compiler {
1409 pub version: String,
1410}
1411
1412#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1413pub struct Output {
1414 pub abi: Vec<SolcAbi>,
1415 pub devdoc: Option<Doc>,
1416 pub userdoc: Option<Doc>,
1417}
1418
1419#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1420pub struct SolcAbi {
1421 #[serde(default)]
1422 pub inputs: Vec<Item>,
1423 #[serde(rename = "stateMutability", skip_serializing_if = "Option::is_none")]
1424 pub state_mutability: Option<String>,
1425 #[serde(rename = "type")]
1426 pub abi_type: String,
1427 #[serde(default, skip_serializing_if = "Option::is_none")]
1428 pub name: Option<String>,
1429 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1430 pub outputs: Vec<Item>,
1431 #[serde(default, skip_serializing_if = "Option::is_none")]
1433 pub anonymous: Option<bool>,
1434}
1435
1436#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1437pub struct Item {
1438 #[serde(rename = "internalType")]
1439 pub internal_type: Option<String>,
1440 pub name: String,
1441 #[serde(rename = "type")]
1442 pub put_type: String,
1443 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1444 pub components: Vec<Item>,
1445 #[serde(default, skip_serializing_if = "Option::is_none")]
1447 pub indexed: Option<bool>,
1448}
1449
1450#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1451pub struct Doc {
1452 #[serde(default, skip_serializing_if = "Option::is_none")]
1453 pub kind: Option<String>,
1454 #[serde(default, skip_serializing_if = "Option::is_none")]
1455 pub methods: Option<DocLibraries>,
1456 #[serde(default, skip_serializing_if = "Option::is_none")]
1457 pub version: Option<u32>,
1458}
1459
1460#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1461pub struct DocLibraries {
1462 #[serde(flatten)]
1463 pub libs: BTreeMap<String, serde_json::Value>,
1464}
1465
1466#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1468pub struct CompilerOutput {
1469 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1470 pub errors: Vec<Error>,
1471 #[serde(default)]
1472 pub sources: BTreeMap<PathBuf, SourceFile>,
1473 #[serde(default)]
1474 pub contracts: Contracts,
1475}
1476
1477impl CompilerOutput {
1478 pub fn has_error(&self) -> bool {
1480 self.errors.iter().any(|err| err.severity.is_error())
1481 }
1482
1483 pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1485 self.contracts_iter().find_map(|(name, contract)| {
1486 (name == contract_name).then(|| CompactContractRef::from(contract))
1487 })
1488 }
1489
1490 pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1492 self.contracts.values_mut().find_map(|c| c.remove(contract_name))
1493 }
1494
1495 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1497 self.contracts.values().flatten()
1498 }
1499
1500 pub fn contracts_into_iter(self) -> impl Iterator<Item = (String, Contract)> {
1502 self.contracts.into_values().flatten()
1503 }
1504
1505 pub fn get(&self, path: &Path, contract: &str) -> Option<CompactContractRef<'_>> {
1508 self.contracts
1509 .get(path)
1510 .and_then(|contracts| contracts.get(contract))
1511 .map(CompactContractRef::from)
1512 }
1513
1514 pub fn split(self) -> (SourceFiles, OutputContracts) {
1517 (SourceFiles(self.sources), OutputContracts(self.contracts))
1518 }
1519
1520 pub fn retain_files<'a, I>(&mut self, files: I)
1524 where
1525 I: IntoIterator<Item = &'a Path>,
1526 {
1527 let files: HashSet<_> =
1530 files.into_iter().map(|s| s.to_string_lossy().to_lowercase()).collect();
1531 self.contracts.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1532 self.sources.retain(|f, _| files.contains(&f.to_string_lossy().to_lowercase()));
1533 }
1534
1535 pub fn merge(&mut self, other: Self) {
1536 self.errors.extend(other.errors);
1537 self.contracts.extend(other.contracts);
1538 self.sources.extend(other.sources);
1539 }
1540}
1541
1542#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1544pub struct OutputContracts(pub Contracts);
1545
1546impl OutputContracts {
1547 pub fn into_contracts(self) -> impl Iterator<Item = (String, Contract)> {
1549 self.0.into_values().flatten()
1550 }
1551
1552 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1554 self.0.values().flatten()
1555 }
1556
1557 pub fn find(&self, contract_name: &str) -> Option<CompactContractRef<'_>> {
1559 self.contracts_iter().find_map(|(name, contract)| {
1560 (name == contract_name).then(|| CompactContractRef::from(contract))
1561 })
1562 }
1563
1564 pub fn remove(&mut self, contract_name: &str) -> Option<Contract> {
1566 self.0.values_mut().find_map(|c| c.remove(contract_name))
1567 }
1568}
1569
1570#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1571pub struct UserDoc {
1572 #[serde(default, skip_serializing_if = "Option::is_none")]
1573 pub version: Option<u32>,
1574 #[serde(default, skip_serializing_if = "Option::is_none")]
1575 pub kind: Option<String>,
1576 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1577 pub methods: BTreeMap<String, UserDocNotice>,
1578 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1579 pub events: BTreeMap<String, UserDocNotice>,
1580 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1581 pub errors: BTreeMap<String, Vec<UserDocNotice>>,
1582 #[serde(default, skip_serializing_if = "Option::is_none")]
1583 pub notice: Option<String>,
1584}
1585
1586#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1587#[serde(untagged)]
1588pub enum UserDocNotice {
1589 Constructor(String),
1591 Notice { notice: String },
1592}
1593
1594#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1595pub struct DevDoc {
1596 #[serde(default, skip_serializing_if = "Option::is_none")]
1597 pub version: Option<u32>,
1598 #[serde(default, skip_serializing_if = "Option::is_none")]
1599 pub kind: Option<String>,
1600 #[serde(default, skip_serializing_if = "Option::is_none")]
1601 pub author: Option<String>,
1602 #[serde(default, skip_serializing_if = "Option::is_none")]
1603 pub details: Option<String>,
1604 #[serde(default, rename = "custom:experimental", skip_serializing_if = "Option::is_none")]
1605 pub custom_experimental: Option<String>,
1606 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1607 pub methods: BTreeMap<String, MethodDoc>,
1608 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1609 pub events: BTreeMap<String, EventDoc>,
1610 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1611 pub errors: BTreeMap<String, Vec<ErrorDoc>>,
1612 #[serde(default, skip_serializing_if = "Option::is_none")]
1613 pub title: Option<String>,
1614}
1615
1616#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1617pub struct MethodDoc {
1618 #[serde(default, skip_serializing_if = "Option::is_none")]
1619 pub details: Option<String>,
1620 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1621 pub params: BTreeMap<String, String>,
1622 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1623 pub returns: BTreeMap<String, String>,
1624}
1625
1626#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1627pub struct EventDoc {
1628 #[serde(default, skip_serializing_if = "Option::is_none")]
1629 pub details: Option<String>,
1630 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1631 pub params: BTreeMap<String, String>,
1632}
1633
1634#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1635pub struct ErrorDoc {
1636 #[serde(default, skip_serializing_if = "Option::is_none")]
1637 pub details: Option<String>,
1638 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1639 pub params: BTreeMap<String, String>,
1640}
1641
1642#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1643#[serde(rename_all = "camelCase")]
1644pub struct Evm {
1645 #[serde(default, skip_serializing_if = "Option::is_none")]
1646 pub assembly: Option<String>,
1647 #[serde(default, skip_serializing_if = "Option::is_none")]
1648 pub legacy_assembly: Option<serde_json::Value>,
1649 pub bytecode: Option<Bytecode>,
1650 #[serde(default, skip_serializing_if = "Option::is_none")]
1651 pub deployed_bytecode: Option<DeployedBytecode>,
1652 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1654 pub method_identifiers: BTreeMap<String, String>,
1655 #[serde(default, skip_serializing_if = "Option::is_none")]
1657 pub gas_estimates: Option<GasEstimates>,
1658}
1659
1660impl Evm {
1661 pub(crate) fn into_compact(self) -> CompactEvm {
1664 let Self {
1665 assembly,
1666 legacy_assembly,
1667 bytecode,
1668 deployed_bytecode,
1669 method_identifiers,
1670 gas_estimates,
1671 } = self;
1672
1673 let (bytecode, deployed_bytecode) = match (bytecode, deployed_bytecode) {
1674 (Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())),
1675 (None, Some(dbcode)) => (None, Some(dbcode.into())),
1676 (Some(bcode), None) => (Some(bcode.into()), None),
1677 (None, None) => (None, None),
1678 };
1679
1680 CompactEvm {
1681 assembly,
1682 legacy_assembly,
1683 bytecode,
1684 deployed_bytecode,
1685 method_identifiers,
1686 gas_estimates,
1687 }
1688 }
1689}
1690
1691#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1692#[serde(rename_all = "camelCase")]
1693pub(crate) struct CompactEvm {
1694 #[serde(default, skip_serializing_if = "Option::is_none")]
1695 pub assembly: Option<String>,
1696 #[serde(default, skip_serializing_if = "Option::is_none")]
1697 pub legacy_assembly: Option<serde_json::Value>,
1698 pub bytecode: Option<CompactBytecode>,
1699 #[serde(default, skip_serializing_if = "Option::is_none")]
1700 pub deployed_bytecode: Option<CompactDeployedBytecode>,
1701 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1703 pub method_identifiers: BTreeMap<String, String>,
1704 #[serde(default, skip_serializing_if = "Option::is_none")]
1706 pub gas_estimates: Option<GasEstimates>,
1707}
1708
1709#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1710#[serde(rename_all = "camelCase")]
1711pub struct FunctionDebugData {
1712 pub entry_point: Option<u32>,
1713 pub id: Option<u32>,
1714 pub parameter_slots: Option<u32>,
1715 pub return_slots: Option<u32>,
1716}
1717
1718#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1719pub struct GeneratedSource {
1720 pub ast: serde_json::Value,
1721 pub contents: String,
1722 pub id: u32,
1723 pub language: String,
1724 pub name: String,
1725}
1726
1727#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1730pub struct Offsets {
1731 pub start: u32,
1732 pub length: u32,
1733}
1734
1735#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1736pub struct GasEstimates {
1737 pub creation: Creation,
1738 #[serde(default)]
1739 pub external: BTreeMap<String, String>,
1740 #[serde(default)]
1741 pub internal: BTreeMap<String, String>,
1742}
1743
1744#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1745#[serde(rename_all = "camelCase")]
1746pub struct Creation {
1747 pub code_deposit_cost: String,
1748 pub execution_cost: String,
1749 pub total_cost: String,
1750}
1751
1752#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1753pub struct Ewasm {
1754 #[serde(default, skip_serializing_if = "Option::is_none")]
1755 pub wast: Option<String>,
1756 pub wasm: String,
1757}
1758
1759#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1761pub struct StorageLayout {
1762 pub storage: Vec<Storage>,
1763 #[serde(default, deserialize_with = "serde_helpers::default_for_null")]
1764 pub types: BTreeMap<String, StorageType>,
1765}
1766
1767impl StorageLayout {
1768 fn is_empty(&self) -> bool {
1769 self.storage.is_empty() && self.types.is_empty()
1770 }
1771}
1772
1773#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1774pub struct Storage {
1775 #[serde(rename = "astId")]
1776 pub ast_id: u64,
1777 pub contract: String,
1778 pub label: String,
1779 pub offset: i64,
1780 pub slot: String,
1781 #[serde(rename = "type")]
1782 pub storage_type: String,
1783}
1784
1785#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1786pub struct StorageType {
1787 pub encoding: String,
1788 #[serde(default, skip_serializing_if = "Option::is_none")]
1789 pub key: Option<String>,
1790 pub label: String,
1791 #[serde(rename = "numberOfBytes")]
1792 pub number_of_bytes: String,
1793 #[serde(default, skip_serializing_if = "Option::is_none")]
1794 pub value: Option<String>,
1795 #[serde(flatten)]
1797 pub other: BTreeMap<String, serde_json::Value>,
1798}
1799
1800#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1801pub struct SourceFile {
1802 pub id: u32,
1803 #[serde(default, with = "serde_helpers::empty_json_object_opt")]
1804 pub ast: Option<Ast>,
1805}
1806
1807impl SourceFile {
1808 pub fn contains_contract_definition(&self) -> bool {
1811 self.ast.as_ref().is_some_and(|ast| {
1812 ast.nodes.iter().any(|node| matches!(node.node_type, NodeType::ContractDefinition))
1813 })
1814 }
1815}
1816
1817#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1819pub struct SourceFiles(pub BTreeMap<PathBuf, SourceFile>);
1820
1821impl SourceFiles {
1822 pub fn into_ids(self) -> impl Iterator<Item = (u32, PathBuf)> {
1824 self.0.into_iter().map(|(k, v)| (v.id, k))
1825 }
1826
1827 pub fn into_paths(self) -> impl Iterator<Item = (PathBuf, u32)> {
1829 self.0.into_iter().map(|(k, v)| (k, v.id))
1830 }
1831}
1832
1833#[cfg(test)]
1834mod tests {
1835 use super::*;
1836 use alloy_primitives::Address;
1837 use similar_asserts::assert_eq;
1838 use std::fs;
1839
1840 #[test]
1841 fn can_link_bytecode() {
1842 #[derive(Serialize, Deserialize)]
1845 struct Mockject {
1846 object: BytecodeObject,
1847 }
1848 fn parse_bytecode(bytecode: &str) -> BytecodeObject {
1849 let object: Mockject =
1850 serde_json::from_value(serde_json::json!({ "object": bytecode })).unwrap();
1851 object.object
1852 }
1853
1854 let bytecode = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1855
1856 let mut object = parse_bytecode(bytecode);
1857 assert!(object.is_unlinked());
1858 assert!(object.contains_placeholder("lib2.sol", "L"));
1859 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1860 assert!(object.link("lib2.sol", "L", Address::random()).resolve().is_some());
1861 assert!(!object.is_unlinked());
1862
1863 let mut code = Bytecode {
1864 function_debug_data: Default::default(),
1865 object: parse_bytecode(bytecode),
1866 opcodes: None,
1867 source_map: None,
1868 generated_sources: vec![],
1869 link_references: BTreeMap::from([(
1870 "lib2.sol".to_string(),
1871 BTreeMap::from([("L".to_string(), vec![])]),
1872 )]),
1873 };
1874
1875 assert!(!code.link("lib2.sol", "Y", Address::random()));
1876 assert!(code.link("lib2.sol", "L", Address::random()));
1877 assert!(code.link("lib2.sol", "L", Address::random()));
1878
1879 let hashed_placeholder = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__$cb901161e812ceb78cfe30ca65050c4337$__6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
1880 let mut object = parse_bytecode(hashed_placeholder);
1881 assert!(object.is_unlinked());
1882 assert!(object.contains_placeholder("lib2.sol", "L"));
1883 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
1884 assert!(object.link("lib2.sol", "L", Address::default()).resolve().is_some());
1885 assert!(!object.is_unlinked());
1886 }
1887
1888 #[test]
1889 fn can_parse_compiler_output() {
1890 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/out");
1891
1892 for path in fs::read_dir(dir).unwrap() {
1893 let path = path.unwrap().path();
1894 let compiler_output = fs::read_to_string(&path).unwrap();
1895 serde_json::from_str::<CompilerOutput>(&compiler_output).unwrap_or_else(|err| {
1896 panic!("Failed to read compiler output of {} {}", path.display(), err)
1897 });
1898 }
1899 }
1900
1901 #[test]
1902 fn can_parse_compiler_input() {
1903 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1904
1905 for path in fs::read_dir(dir).unwrap() {
1906 let path = path.unwrap().path();
1907 let compiler_input = fs::read_to_string(&path).unwrap();
1908 serde_json::from_str::<SolcInput>(&compiler_input).unwrap_or_else(|err| {
1909 panic!("Failed to read compiler input of {} {}", path.display(), err)
1910 });
1911 }
1912 }
1913
1914 #[test]
1915 fn can_parse_standard_json_compiler_input() {
1916 let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/in");
1917
1918 for path in fs::read_dir(dir).unwrap() {
1919 let path = path.unwrap().path();
1920 let compiler_input = fs::read_to_string(&path).unwrap();
1921 let val = serde_json::from_str::<StandardJsonCompilerInput>(&compiler_input)
1922 .unwrap_or_else(|err| {
1923 panic!("Failed to read compiler output of {} {}", path.display(), err)
1924 });
1925
1926 let pretty = serde_json::to_string_pretty(&val).unwrap();
1927 serde_json::from_str::<SolcInput>(&pretty).unwrap_or_else(|err| {
1928 panic!("Failed to read converted compiler input of {} {}", path.display(), err)
1929 });
1930 }
1931 }
1932
1933 #[test]
1934 fn test_evm_version_default() {
1935 for &(solc_version, expected) in &[
1936 ("0.4.20", None),
1938 ("0.4.21", Some(EvmVersion::Byzantium)),
1940 ("0.4.22", Some(EvmVersion::Byzantium)),
1942 ("0.5.5", Some(EvmVersion::Petersburg)),
1944 ("0.5.14", Some(EvmVersion::Istanbul)),
1946 ("0.8.5", Some(EvmVersion::Berlin)),
1948 ("0.8.7", Some(EvmVersion::London)),
1950 ("0.8.18", Some(EvmVersion::Paris)),
1952 ("0.8.20", Some(EvmVersion::Shanghai)),
1954 ("0.8.24", Some(EvmVersion::Shanghai)),
1956 ("0.8.25", Some(EvmVersion::Cancun)),
1957 ] {
1958 let version = Version::from_str(solc_version).unwrap();
1959 assert_eq!(
1960 EvmVersion::default_version_solc(&version),
1961 expected,
1962 "({version}, {expected:?})"
1963 )
1964 }
1965 }
1966
1967 #[test]
1968 fn test_evm_version_normalization() {
1969 for &(solc_version, evm_version, expected) in &[
1970 ("0.4.20", EvmVersion::Homestead, None),
1972 ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1974 ("0.4.21", EvmVersion::Constantinople, Some(EvmVersion::Byzantium)),
1975 ("0.4.21", EvmVersion::London, Some(EvmVersion::Byzantium)),
1976 ("0.4.22", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1978 ("0.4.22", EvmVersion::Constantinople, Some(EvmVersion::Constantinople)),
1979 ("0.4.22", EvmVersion::London, Some(EvmVersion::Constantinople)),
1980 ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1982 ("0.5.5", EvmVersion::Petersburg, Some(EvmVersion::Petersburg)),
1983 ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)),
1984 ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1986 ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)),
1987 ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)),
1988 ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1990 ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)),
1991 ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)),
1992 ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1994 ("0.8.7", EvmVersion::London, Some(EvmVersion::London)),
1995 ("0.8.7", EvmVersion::Paris, Some(EvmVersion::London)),
1996 ("0.8.18", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
1998 ("0.8.18", EvmVersion::Paris, Some(EvmVersion::Paris)),
1999 ("0.8.18", EvmVersion::Shanghai, Some(EvmVersion::Paris)),
2000 ("0.8.20", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2002 ("0.8.20", EvmVersion::Paris, Some(EvmVersion::Paris)),
2003 ("0.8.20", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2004 ("0.8.20", EvmVersion::Cancun, Some(EvmVersion::Shanghai)),
2005 ("0.8.24", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2007 ("0.8.24", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2008 ("0.8.24", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2009 ("0.8.26", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2011 ("0.8.26", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2012 ("0.8.26", EvmVersion::Cancun, Some(EvmVersion::Cancun)),
2013 ("0.8.26", EvmVersion::Prague, Some(EvmVersion::Cancun)),
2014 ("0.8.27", EvmVersion::Prague, Some(EvmVersion::Prague)),
2015 ("0.8.29", EvmVersion::Osaka, Some(EvmVersion::Osaka)),
2016 ] {
2017 let version = Version::from_str(solc_version).unwrap();
2018 assert_eq!(
2019 evm_version.normalize_version_solc(&version),
2020 expected,
2021 "({version}, {evm_version:?})"
2022 )
2023 }
2024 }
2025
2026 #[test]
2027 fn can_sanitize_byte_code_hash() {
2028 let settings = Settings { metadata: Some(BytecodeHash::Ipfs.into()), ..Default::default() };
2029
2030 let input =
2031 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2032
2033 let i = input.clone().sanitized(&Version::new(0, 6, 0));
2034 assert_eq!(i.settings.metadata.unwrap().bytecode_hash, Some(BytecodeHash::Ipfs));
2035
2036 let i = input.sanitized(&Version::new(0, 5, 17));
2037 assert!(i.settings.metadata.unwrap().bytecode_hash.is_none());
2038 }
2039
2040 #[test]
2041 fn can_sanitize_cbor_metadata() {
2042 let settings = Settings {
2043 metadata: Some(SettingsMetadata::new(BytecodeHash::Ipfs, true)),
2044 ..Default::default()
2045 };
2046
2047 let input =
2048 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2049
2050 let i = input.clone().sanitized(&Version::new(0, 8, 18));
2051 assert_eq!(i.settings.metadata.unwrap().cbor_metadata, Some(true));
2052
2053 let i = input.sanitized(&Version::new(0, 8, 0));
2054 assert!(i.settings.metadata.unwrap().cbor_metadata.is_none());
2055 }
2056
2057 #[test]
2058 fn can_parse_libraries() {
2059 let libraries = ["./src/lib/LibraryContract.sol:Library:0xaddress".to_string()];
2060
2061 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2062
2063 assert_eq!(
2064 libs,
2065 BTreeMap::from([(
2066 PathBuf::from("./src/lib/LibraryContract.sol"),
2067 BTreeMap::from([("Library".to_string(), "0xaddress".to_string())])
2068 )])
2069 );
2070 }
2071
2072 #[test]
2073 fn can_strip_libraries_path_prefixes() {
2074 let libraries= [
2075 "/global/root/src/FileInSrc.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2076 "src/deep/DeepFileInSrc.sol:ChainlinkTWAP:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2077 "/global/GlobalFile.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2078 "/global/root/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2079 "test/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2080 ];
2081
2082 let libs = Libraries::parse(&libraries[..])
2083 .unwrap()
2084 .with_stripped_file_prefixes("/global/root".as_ref())
2085 .libs;
2086
2087 assert_eq!(
2088 libs,
2089 BTreeMap::from([
2090 (
2091 PathBuf::from("/global/GlobalFile.sol"),
2092 BTreeMap::from([(
2093 "Math".to_string(),
2094 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2095 )])
2096 ),
2097 (
2098 PathBuf::from("src/FileInSrc.sol"),
2099 BTreeMap::from([(
2100 "Chainlink".to_string(),
2101 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2102 )])
2103 ),
2104 (
2105 PathBuf::from("src/deep/DeepFileInSrc.sol"),
2106 BTreeMap::from([(
2107 "ChainlinkTWAP".to_string(),
2108 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2109 )])
2110 ),
2111 (
2112 PathBuf::from("test/SizeAuctionDiscount.sol"),
2113 BTreeMap::from([(
2114 "Math".to_string(),
2115 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2116 )])
2117 ),
2118 (
2119 PathBuf::from("test/ChainlinkTWAP.t.sol"),
2120 BTreeMap::from([(
2121 "ChainlinkTWAP".to_string(),
2122 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2123 )])
2124 ),
2125 ])
2126 );
2127 }
2128
2129 #[test]
2130 fn can_parse_many_libraries() {
2131 let libraries= [
2132 "./src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2133 "./src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2134 "./src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2135 "./src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2136 "./src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2137 ];
2138
2139 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2140
2141 assert_eq!(
2142 libs,
2143 BTreeMap::from([
2144 (
2145 PathBuf::from("./src/SizeAuctionDiscount.sol"),
2146 BTreeMap::from([
2147 (
2148 "Chainlink".to_string(),
2149 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2150 ),
2151 (
2152 "Math".to_string(),
2153 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2154 )
2155 ])
2156 ),
2157 (
2158 PathBuf::from("./src/SizeAuction.sol"),
2159 BTreeMap::from([
2160 (
2161 "ChainlinkTWAP".to_string(),
2162 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2163 ),
2164 (
2165 "Math".to_string(),
2166 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2167 )
2168 ])
2169 ),
2170 (
2171 PathBuf::from("./src/test/ChainlinkTWAP.t.sol"),
2172 BTreeMap::from([(
2173 "ChainlinkTWAP".to_string(),
2174 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2175 )])
2176 ),
2177 ])
2178 );
2179 }
2180
2181 #[test]
2182 fn test_lossless_metadata() {
2183 #[derive(Debug, Serialize, Deserialize)]
2184 #[serde(rename_all = "camelCase")]
2185 pub struct Contract {
2186 #[serde(
2187 default,
2188 skip_serializing_if = "Option::is_none",
2189 with = "serde_helpers::json_string_opt"
2190 )]
2191 pub metadata: Option<LosslessMetadata>,
2192 }
2193
2194 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}"}"#;
2195
2196 let value: serde_json::Value = serde_json::from_str(s).unwrap();
2197 let c: Contract = serde_json::from_value(value).unwrap();
2198 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}");
2199
2200 let value = serde_json::to_string(&c).unwrap();
2201 assert_eq!(s, value);
2202 }
2203
2204 #[test]
2205 fn test_lossless_storage_layout() {
2206 let input = include_str!("../../../../test-data/foundryissue2462.json").trim();
2207 let layout: StorageLayout = serde_json::from_str(input).unwrap();
2208 assert_eq!(input, &serde_json::to_string(&layout).unwrap());
2209 }
2210
2211 #[test]
2213 fn can_parse_compiler_output_spells_0_6_12() {
2214 let path =
2215 Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../test-data/0.6.12-with-libs.json");
2216 let content = fs::read_to_string(path).unwrap();
2217 let _output: CompilerOutput = serde_json::from_str(&content).unwrap();
2218 }
2219
2220 #[test]
2222 fn can_sanitize_optimizer_inliner() {
2223 let settings = Settings::default().with_via_ir_minimum_optimization();
2224
2225 let input =
2226 SolcInput { language: SolcLanguage::Solidity, sources: Default::default(), settings };
2227
2228 let i = input.clone().sanitized(&Version::new(0, 8, 4));
2229 assert!(i.settings.optimizer.details.unwrap().inliner.is_none());
2230
2231 let i = input.sanitized(&Version::new(0, 8, 5));
2232 assert_eq!(i.settings.optimizer.details.unwrap().inliner, Some(false));
2233 }
2234}