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