1use crate::{
3 compile::*, error::SolcIoError, remappings::Remapping, utils, ProjectPathsConfig, SolcError,
4};
5use ethers_core::abi::Abi;
6use md5::Digest;
7use semver::Version;
8use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
9use std::{
10 collections::{BTreeMap, HashSet},
11 fmt, fs,
12 ops::Range,
13 path::{Path, PathBuf},
14 str::FromStr,
15 sync::Arc,
16};
17use tracing::warn;
18use yansi::{Color, Paint, Style};
19
20pub mod ast;
21pub use ast::*;
22pub mod bytecode;
23pub mod contract;
24pub mod output_selection;
25pub mod serde_helpers;
26use crate::{
27 artifacts::output_selection::{ContractOutputSelection, OutputSelection},
28 filter::FilteredSources,
29};
30pub use bytecode::*;
31pub use contract::*;
32pub use serde_helpers::{deserialize_bytes, deserialize_opt_bytes};
33
34pub type FileToContractsMap<T> = BTreeMap<String, BTreeMap<String, T>>;
40
41pub type Contracts = FileToContractsMap<Contract>;
43
44pub type Sources = BTreeMap<PathBuf, Source>;
46
47pub(crate) type VersionedSources = BTreeMap<Solc, (Version, Sources)>;
49
50pub(crate) type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>;
52
53const SOLIDITY: &str = "Solidity";
54const YUL: &str = "Yul";
55
56#[derive(Clone, Debug, Serialize, Deserialize)]
58pub struct CompilerInput {
59 pub language: String,
60 pub sources: Sources,
61 pub settings: Settings,
62}
63
64impl CompilerInput {
65 pub fn new(path: impl AsRef<Path>) -> Result<Vec<Self>, SolcIoError> {
67 Source::read_all_from(path.as_ref()).map(Self::with_sources)
68 }
69
70 pub fn with_sources(sources: Sources) -> Vec<Self> {
75 let mut solidity_sources = BTreeMap::new();
76 let mut yul_sources = BTreeMap::new();
77 for (path, source) in sources {
78 if path.extension() == Some(std::ffi::OsStr::new("yul")) {
79 yul_sources.insert(path, source);
80 } else {
81 solidity_sources.insert(path, source);
82 }
83 }
84 let mut res = Vec::new();
85 if !solidity_sources.is_empty() {
86 res.push(Self {
87 language: SOLIDITY.to_string(),
88 sources: solidity_sources,
89 settings: Default::default(),
90 });
91 }
92 if !yul_sources.is_empty() {
93 res.push(Self {
94 language: YUL.to_string(),
95 sources: yul_sources,
96 settings: Default::default(),
97 });
98 }
99 res
100 }
101
102 pub fn sanitize(&mut self, version: &Version) {
105 self.settings.sanitize(version)
106 }
107
108 pub fn sanitized(mut self, version: &Version) -> Self {
110 self.settings.sanitize(version);
111 self
112 }
113
114 #[must_use]
116 pub fn settings(mut self, mut settings: Settings) -> Self {
117 if self.is_yul() {
118 if !settings.remappings.is_empty() {
119 warn!("omitting remappings supplied for the yul sources");
120 settings.remappings = vec![];
121 }
122 if let Some(debug) = settings.debug.as_mut() {
123 if debug.revert_strings.is_some() {
124 warn!("omitting revertStrings supplied for the yul sources");
125 debug.revert_strings = None;
126 }
127 }
128 }
129 self.settings = settings;
130 self
131 }
132
133 #[must_use]
135 pub fn evm_version(mut self, version: EvmVersion) -> Self {
136 self.settings.evm_version = Some(version);
137 self
138 }
139
140 #[must_use]
142 pub fn optimizer(mut self, runs: usize) -> Self {
143 self.settings.optimizer.runs(runs);
144 self
145 }
146
147 #[must_use]
150 pub fn normalize_evm_version(mut self, version: &Version) -> Self {
151 if let Some(evm_version) = &mut self.settings.evm_version {
152 self.settings.evm_version = evm_version.normalize_version(version);
153 }
154 self
155 }
156
157 #[must_use]
158 pub fn with_remappings(mut self, remappings: Vec<Remapping>) -> Self {
159 if self.is_yul() {
160 warn!("omitting remappings supplied for the yul sources");
161 } else {
162 self.settings.remappings = remappings;
163 }
164 self
165 }
166
167 #[must_use]
169 pub fn join_path(mut self, root: impl AsRef<Path>) -> Self {
170 let root = root.as_ref();
171 self.sources = self.sources.into_iter().map(|(path, s)| (root.join(path), s)).collect();
172 self
173 }
174
175 pub fn strip_prefix(mut self, base: impl AsRef<Path>) -> Self {
177 let base = base.as_ref();
178 self.sources = self
179 .sources
180 .into_iter()
181 .map(|(path, s)| (path.strip_prefix(base).map(Into::into).unwrap_or(path), s))
182 .collect();
183 self
184 }
185
186 pub fn with_base_path(mut self, base: impl AsRef<Path>) -> Self {
191 let base = base.as_ref();
192 self.settings = self.settings.with_base_path(base);
193 self.strip_prefix(base)
194 }
195
196 pub fn is_yul(&self) -> bool {
199 self.language == YUL
200 }
201}
202
203#[derive(Clone, Debug, Serialize, Deserialize)]
210pub struct StandardJsonCompilerInput {
211 pub language: String,
212 #[serde(with = "serde_helpers::tuple_vec_map")]
213 pub sources: Vec<(PathBuf, Source)>,
214 pub settings: Settings,
215}
216
217impl StandardJsonCompilerInput {
220 pub fn new(sources: Vec<(PathBuf, Source)>, settings: Settings) -> Self {
221 Self { language: SOLIDITY.to_string(), sources, settings }
222 }
223
224 #[must_use]
227 pub fn normalize_evm_version(mut self, version: &Version) -> Self {
228 if let Some(evm_version) = &mut self.settings.evm_version {
229 self.settings.evm_version = evm_version.normalize_version(version);
230 }
231 self
232 }
233}
234
235impl From<StandardJsonCompilerInput> for CompilerInput {
236 fn from(input: StandardJsonCompilerInput) -> Self {
237 let StandardJsonCompilerInput { language, sources, settings } = input;
238 CompilerInput { language, sources: sources.into_iter().collect(), settings }
239 }
240}
241
242impl From<CompilerInput> for StandardJsonCompilerInput {
243 fn from(input: CompilerInput) -> Self {
244 let CompilerInput { language, sources, settings } = input;
245 StandardJsonCompilerInput { language, sources: sources.into_iter().collect(), settings }
246 }
247}
248
249#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct Settings {
252 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub stop_after: Option<String>,
256 #[serde(default, skip_serializing_if = "Vec::is_empty")]
257 pub remappings: Vec<Remapping>,
258 pub optimizer: Optimizer,
259 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub model_checker: Option<ModelCheckerSettings>,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub metadata: Option<SettingsMetadata>,
265 #[serde(default)]
270 pub output_selection: OutputSelection,
271 #[serde(
272 default,
273 with = "serde_helpers::display_from_str_opt",
274 skip_serializing_if = "Option::is_none"
275 )]
276 pub evm_version: Option<EvmVersion>,
277 #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
280 pub via_ir: Option<bool>,
281 #[serde(default, skip_serializing_if = "Option::is_none")]
282 pub debug: Option<DebuggingSettings>,
283 #[serde(default)]
291 pub libraries: Libraries,
292}
293
294impl Settings {
295 pub fn new(output_selection: impl Into<OutputSelection>) -> Self {
297 Self { output_selection: output_selection.into(), ..Default::default() }
298 }
299
300 pub fn sanitized(mut self, version: &Version) -> Self {
302 self.sanitize(version);
303 self
304 }
305
306 pub fn sanitize(&mut self, version: &Version) {
308 const V0_6_0: Version = Version::new(0, 6, 0);
309 if *version < V0_6_0 {
310 if let Some(meta) = &mut self.metadata {
311 meta.bytecode_hash = None;
314 }
315 self.debug = None;
317 }
318
319 const V0_7_5: Version = Version::new(0, 7, 5);
320 if *version < V0_7_5 {
321 self.via_ir = None;
323 }
324
325 const V0_8_7: Version = Version::new(0, 8, 7);
326 if *version < V0_8_7 {
327 self.model_checker = None;
331 }
332
333 const V0_8_10: Version = Version::new(0, 8, 10);
334 if *version < V0_8_10 {
335 if let Some(debug) = &mut self.debug {
336 debug.debug_info.clear();
339 }
340
341 if let Some(model_checker) = &mut self.model_checker {
342 model_checker.invariants = None;
344 }
345 }
346
347 const V0_8_18: Version = Version::new(0, 8, 18);
348 if *version < V0_8_18 {
349 if let Some(meta) = &mut self.metadata {
351 meta.cbor_metadata = None;
352 }
353
354 if let Some(model_checker) = &mut self.model_checker {
355 if let Some(solvers) = &mut model_checker.solvers {
356 solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
358 }
359 }
360 }
361
362 if *version < SHANGHAI_SOLC {
363 if let Some(model_checker) = &mut self.model_checker {
365 model_checker.show_proved_safe = None;
366 model_checker.show_unsupported = None;
367 }
368 }
369 }
370
371 pub fn push_all(&mut self, settings: impl IntoIterator<Item = ContractOutputSelection>) {
373 for value in settings {
374 self.push_output_selection(value)
375 }
376 }
377
378 #[must_use]
380 pub fn with_extra_output(
381 mut self,
382 settings: impl IntoIterator<Item = ContractOutputSelection>,
383 ) -> Self {
384 for value in settings {
385 self.push_output_selection(value)
386 }
387 self
388 }
389
390 pub fn push_output_selection(&mut self, value: impl ToString) {
399 self.push_contract_output_selection("*", value)
400 }
401
402 pub fn push_contract_output_selection(
406 &mut self,
407 contracts: impl Into<String>,
408 value: impl ToString,
409 ) {
410 let value = value.to_string();
411 let values = self
412 .output_selection
413 .as_mut()
414 .entry("*".to_string())
415 .or_default()
416 .entry(contracts.into())
417 .or_default();
418 if !values.contains(&value) {
419 values.push(value)
420 }
421 }
422
423 pub fn set_output_selection(&mut self, values: impl IntoIterator<Item = impl ToString>) {
425 self.set_contract_output_selection("*", values)
426 }
427
428 pub fn set_contract_output_selection(
432 &mut self,
433 key: impl Into<String>,
434 values: impl IntoIterator<Item = impl ToString>,
435 ) {
436 self.output_selection
437 .as_mut()
438 .entry("*".to_string())
439 .or_default()
440 .insert(key.into(), values.into_iter().map(|s| s.to_string()).collect());
441 }
442
443 #[must_use]
445 pub fn set_via_ir(mut self, via_ir: bool) -> Self {
446 self.via_ir = Some(via_ir);
447 self
448 }
449
450 #[must_use]
452 pub fn with_via_ir(self) -> Self {
453 self.set_via_ir(true)
454 }
455
456 pub fn with_via_ir_minimum_optimization(mut self) -> Self {
463 self.via_ir = Some(true);
466 self.optimizer.details = Some(OptimizerDetails {
467 peephole: Some(false),
468 inliner: Some(false),
469 jumpdest_remover: Some(false),
470 order_literals: Some(false),
471 deduplicate: Some(false),
472 cse: Some(false),
473 constant_optimizer: Some(false),
474 yul: Some(true), yul_details: Some(YulDetails {
476 stack_allocation: Some(true),
477 optimizer_steps: Some("u".to_string()),
479 }),
480 });
481 self
482 }
483
484 #[must_use]
486 pub fn with_ast(mut self) -> Self {
487 let output =
488 self.output_selection.as_mut().entry("*".to_string()).or_insert_with(BTreeMap::default);
489 output.insert("".to_string(), vec!["ast".to_string()]);
490 self
491 }
492
493 pub fn with_base_path(mut self, base: impl AsRef<Path>) -> Self {
495 let base = base.as_ref();
496 self.remappings.iter_mut().for_each(|r| {
497 r.strip_prefix(base);
498 });
499
500 self.libraries.libs = self
501 .libraries
502 .libs
503 .into_iter()
504 .map(|(file, libs)| (file.strip_prefix(base).map(Into::into).unwrap_or(file), libs))
505 .collect();
506
507 self.output_selection = OutputSelection(
508 self.output_selection
509 .0
510 .into_iter()
511 .map(|(file, selection)| {
512 (
513 Path::new(&file)
514 .strip_prefix(base)
515 .map(|p| format!("{}", p.display()))
516 .unwrap_or(file),
517 selection,
518 )
519 })
520 .collect(),
521 );
522
523 if let Some(mut model_checker) = self.model_checker.take() {
524 model_checker.contracts = model_checker
525 .contracts
526 .into_iter()
527 .map(|(path, contracts)| {
528 (
529 Path::new(&path)
530 .strip_prefix(base)
531 .map(|p| format!("{}", p.display()))
532 .unwrap_or(path),
533 contracts,
534 )
535 })
536 .collect();
537 self.model_checker = Some(model_checker);
538 }
539
540 self
541 }
542}
543
544impl Default for Settings {
545 fn default() -> Self {
546 Self {
547 stop_after: None,
548 optimizer: Default::default(),
549 metadata: None,
550 output_selection: OutputSelection::default_output_selection(),
551 evm_version: Some(EvmVersion::default()),
552 via_ir: None,
553 debug: None,
554 libraries: Default::default(),
555 remappings: Default::default(),
556 model_checker: None,
557 }
558 .with_ast()
559 }
560}
561
562#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
564#[serde(transparent)]
565pub struct Libraries {
566 pub libs: BTreeMap<PathBuf, BTreeMap<String, String>>,
568}
569
570impl Libraries {
573 pub fn parse(libs: &[String]) -> Result<Self, SolcError> {
586 let mut libraries = BTreeMap::default();
587 for lib in libs {
588 let mut items = lib.split(':');
589 let file = items.next().ok_or_else(|| {
590 SolcError::msg(format!("failed to parse path to library file: {lib}"))
591 })?;
592 let lib = items
593 .next()
594 .ok_or_else(|| SolcError::msg(format!("failed to parse library name: {lib}")))?;
595 let addr = items
596 .next()
597 .ok_or_else(|| SolcError::msg(format!("failed to parse library address: {lib}")))?;
598 if items.next().is_some() {
599 return Err(SolcError::msg(format!(
600 "failed to parse, too many arguments passed: {lib}"
601 )))
602 }
603 libraries
604 .entry(file.into())
605 .or_insert_with(BTreeMap::default)
606 .insert(lib.to_string(), addr.to_string());
607 }
608 Ok(Self { libs: libraries })
609 }
610
611 pub fn is_empty(&self) -> bool {
612 self.libs.is_empty()
613 }
614
615 pub fn len(&self) -> usize {
616 self.libs.len()
617 }
618
619 pub fn with_applied_remappings(mut self, config: &ProjectPathsConfig) -> Self {
623 self.libs = self
624 .libs
625 .into_iter()
626 .map(|(file, target)| {
627 let file = config.resolve_import(&config.root, &file).unwrap_or_else(|err| {
628 warn!(target: "libs", "Failed to resolve library `{}` for linking: {:?}", file.display(), err);
629 file
630 });
631 (file, target)
632 })
633 .collect();
634 self
635 }
636}
637
638impl From<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
639 fn from(libs: BTreeMap<PathBuf, BTreeMap<String, String>>) -> Self {
640 Self { libs }
641 }
642}
643
644impl AsRef<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
645 fn as_ref(&self) -> &BTreeMap<PathBuf, BTreeMap<String, String>> {
646 &self.libs
647 }
648}
649
650impl AsMut<BTreeMap<PathBuf, BTreeMap<String, String>>> for Libraries {
651 fn as_mut(&mut self) -> &mut BTreeMap<PathBuf, BTreeMap<String, String>> {
652 &mut self.libs
653 }
654}
655
656#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
657pub struct Optimizer {
658 #[serde(default, skip_serializing_if = "Option::is_none")]
659 pub enabled: Option<bool>,
660 #[serde(default, skip_serializing_if = "Option::is_none")]
661 pub runs: Option<usize>,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
666 pub details: Option<OptimizerDetails>,
667}
668
669impl Optimizer {
670 pub fn runs(&mut self, runs: usize) {
671 self.runs = Some(runs);
672 }
673
674 pub fn disable(&mut self) {
675 self.enabled.take();
676 }
677
678 pub fn enable(&mut self) {
679 self.enabled = Some(true)
680 }
681}
682
683impl Default for Optimizer {
684 fn default() -> Self {
685 Self { enabled: Some(false), runs: Some(200), details: None }
686 }
687}
688
689#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
690#[serde(rename_all = "camelCase")]
691pub struct OptimizerDetails {
692 #[serde(default, skip_serializing_if = "Option::is_none")]
695 pub peephole: Option<bool>,
696 #[serde(default, skip_serializing_if = "Option::is_none")]
699 pub inliner: Option<bool>,
700 #[serde(default, skip_serializing_if = "Option::is_none")]
703 pub jumpdest_remover: Option<bool>,
704 #[serde(default, skip_serializing_if = "Option::is_none")]
706 pub order_literals: Option<bool>,
707 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub deduplicate: Option<bool>,
710 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub cse: Option<bool>,
714 #[serde(default, skip_serializing_if = "Option::is_none")]
716 pub constant_optimizer: Option<bool>,
717 #[serde(default, skip_serializing_if = "Option::is_none")]
723 pub yul: Option<bool>,
724 #[serde(default, skip_serializing_if = "Option::is_none")]
726 pub yul_details: Option<YulDetails>,
727}
728
729impl OptimizerDetails {
732 pub fn is_empty(&self) -> bool {
734 self.peephole.is_none() &&
735 self.inliner.is_none() &&
736 self.jumpdest_remover.is_none() &&
737 self.order_literals.is_none() &&
738 self.deduplicate.is_none() &&
739 self.cse.is_none() &&
740 self.constant_optimizer.is_none() &&
741 self.yul.is_none() &&
742 self.yul_details.as_ref().map(|yul| yul.is_empty()).unwrap_or(true)
743 }
744}
745
746#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
747#[serde(rename_all = "camelCase")]
748pub struct YulDetails {
749 #[serde(default, skip_serializing_if = "Option::is_none")]
752 pub stack_allocation: Option<bool>,
753 #[serde(default, skip_serializing_if = "Option::is_none")]
756 pub optimizer_steps: Option<String>,
757}
758
759impl YulDetails {
762 pub fn is_empty(&self) -> bool {
764 self.stack_allocation.is_none() && self.optimizer_steps.is_none()
765 }
766}
767
768#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
772pub enum EvmVersion {
773 Homestead,
774 TangerineWhistle,
775 SpuriousDragon,
776 Byzantium,
777 Constantinople,
778 Petersburg,
779 Istanbul,
780 Berlin,
781 London,
782 Paris,
783 #[default]
784 Shanghai,
785}
786
787impl EvmVersion {
788 pub fn normalize_version(self, version: &Version) -> Option<Self> {
790 if *version >= BYZANTIUM_SOLC {
792 let normalized = if *version >= SHANGHAI_SOLC {
795 self
796 } else if self >= Self::Paris && *version >= PARIS_SOLC {
797 Self::Paris
798 } else if self >= Self::London && *version >= LONDON_SOLC {
799 Self::London
800 } else if self >= Self::Berlin && *version >= BERLIN_SOLC {
801 Self::Berlin
802 } else if self >= Self::Istanbul && *version >= ISTANBUL_SOLC {
803 Self::Istanbul
804 } else if self >= Self::Petersburg && *version >= PETERSBURG_SOLC {
805 Self::Petersburg
806 } else if self >= Self::Constantinople && *version >= CONSTANTINOPLE_SOLC {
807 Self::Constantinople
808 } else if self >= Self::Byzantium {
809 Self::Byzantium
810 } else {
811 self
812 };
813 Some(normalized)
814 } else {
815 None
816 }
817 }
818
819 pub const fn as_str(&self) -> &'static str {
821 match self {
822 Self::Homestead => "homestead",
823 Self::TangerineWhistle => "tangerineWhistle",
824 Self::SpuriousDragon => "spuriousDragon",
825 Self::Byzantium => "byzantium",
826 Self::Constantinople => "constantinople",
827 Self::Petersburg => "petersburg",
828 Self::Istanbul => "istanbul",
829 Self::Berlin => "berlin",
830 Self::London => "london",
831 Self::Paris => "paris",
832 Self::Shanghai => "shanghai",
833 }
834 }
835
836 pub fn supports_returndata(&self) -> bool {
838 *self >= Self::Byzantium
839 }
840
841 pub fn has_static_call(&self) -> bool {
842 *self >= Self::Byzantium
843 }
844
845 pub fn has_bitwise_shifting(&self) -> bool {
846 *self >= Self::Constantinople
847 }
848
849 pub fn has_create2(&self) -> bool {
850 *self >= Self::Constantinople
851 }
852
853 pub fn has_ext_code_hash(&self) -> bool {
854 *self >= Self::Constantinople
855 }
856
857 pub fn has_chain_id(&self) -> bool {
858 *self >= Self::Istanbul
859 }
860
861 pub fn has_self_balance(&self) -> bool {
862 *self >= Self::Istanbul
863 }
864
865 pub fn has_base_fee(&self) -> bool {
866 *self >= Self::London
867 }
868
869 pub fn has_prevrandao(&self) -> bool {
870 *self >= Self::Paris
871 }
872
873 pub fn has_push0(&self) -> bool {
874 *self >= Self::Shanghai
875 }
876}
877
878impl fmt::Display for EvmVersion {
879 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
880 f.write_str(self.as_str())
881 }
882}
883
884impl FromStr for EvmVersion {
885 type Err = String;
886
887 fn from_str(s: &str) -> Result<Self, Self::Err> {
888 match s {
889 "homestead" => Ok(Self::Homestead),
890 "tangerineWhistle" => Ok(Self::TangerineWhistle),
891 "spuriousDragon" => Ok(Self::SpuriousDragon),
892 "byzantium" => Ok(Self::Byzantium),
893 "constantinople" => Ok(Self::Constantinople),
894 "petersburg" => Ok(Self::Petersburg),
895 "istanbul" => Ok(Self::Istanbul),
896 "berlin" => Ok(Self::Berlin),
897 "london" => Ok(Self::London),
898 "paris" => Ok(Self::Paris),
899 "shanghai" => Ok(Self::Shanghai),
900 s => Err(format!("Unknown evm version: {s}")),
901 }
902 }
903}
904
905#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
907#[serde(rename_all = "camelCase")]
908pub struct DebuggingSettings {
909 #[serde(
910 default,
911 with = "serde_helpers::display_from_str_opt",
912 skip_serializing_if = "Option::is_none"
913 )]
914 pub revert_strings: Option<RevertStrings>,
915 #[serde(default, skip_serializing_if = "Vec::is_empty")]
927 pub debug_info: Vec<String>,
928}
929
930#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
932pub enum RevertStrings {
933 #[default]
935 Default,
936 Strip,
939 Debug,
942 VerboseDebug,
945}
946
947impl fmt::Display for RevertStrings {
948 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
949 let string = match self {
950 RevertStrings::Default => "default",
951 RevertStrings::Strip => "strip",
952 RevertStrings::Debug => "debug",
953 RevertStrings::VerboseDebug => "verboseDebug",
954 };
955 write!(f, "{string}")
956 }
957}
958
959impl FromStr for RevertStrings {
960 type Err = String;
961
962 fn from_str(s: &str) -> Result<Self, Self::Err> {
963 match s {
964 "default" => Ok(RevertStrings::Default),
965 "strip" => Ok(RevertStrings::Strip),
966 "debug" => Ok(RevertStrings::Debug),
967 "verboseDebug" | "verbosedebug" => Ok(RevertStrings::VerboseDebug),
968 s => Err(format!("Unknown evm version: {s}")),
969 }
970 }
971}
972
973#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
974pub struct SettingsMetadata {
975 #[serde(default, rename = "useLiteralContent", skip_serializing_if = "Option::is_none")]
977 pub use_literal_content: Option<bool>,
978 #[serde(
983 default,
984 rename = "bytecodeHash",
985 skip_serializing_if = "Option::is_none",
986 with = "serde_helpers::display_from_str_opt"
987 )]
988 pub bytecode_hash: Option<BytecodeHash>,
989 #[serde(default, rename = "appendCBOR", skip_serializing_if = "Option::is_none")]
990 pub cbor_metadata: Option<bool>,
991}
992
993impl SettingsMetadata {
994 pub fn new(hash: BytecodeHash, cbor: bool) -> Self {
995 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: Some(cbor) }
996 }
997}
998
999impl From<BytecodeHash> for SettingsMetadata {
1000 fn from(hash: BytecodeHash) -> Self {
1001 Self { use_literal_content: None, bytecode_hash: Some(hash), cbor_metadata: None }
1002 }
1003}
1004
1005#[derive(Clone, Debug, Default, Copy, PartialEq, Eq, Serialize, Deserialize)]
1009pub enum BytecodeHash {
1010 #[default]
1011 Ipfs,
1012 None,
1013 Bzzr1,
1014}
1015
1016impl FromStr for BytecodeHash {
1017 type Err = String;
1018
1019 fn from_str(s: &str) -> Result<Self, Self::Err> {
1020 match s {
1021 "none" => Ok(BytecodeHash::None),
1022 "ipfs" => Ok(BytecodeHash::Ipfs),
1023 "bzzr1" => Ok(BytecodeHash::Bzzr1),
1024 s => Err(format!("Unknown bytecode hash: {s}")),
1025 }
1026 }
1027}
1028
1029impl fmt::Display for BytecodeHash {
1030 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1031 let s = match self {
1032 BytecodeHash::Ipfs => "ipfs",
1033 BytecodeHash::None => "none",
1034 BytecodeHash::Bzzr1 => "bzzr1",
1035 };
1036 f.write_str(s)
1037 }
1038}
1039
1040#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1042pub struct Metadata {
1043 pub compiler: Compiler,
1044 pub language: String,
1045 pub output: Output,
1046 pub settings: MetadataSettings,
1047 pub sources: MetadataSources,
1048 pub version: i64,
1049}
1050
1051#[derive(Clone, Debug, PartialEq, Eq)]
1054pub struct LosslessMetadata {
1055 pub raw_metadata: String,
1057 pub metadata: Metadata,
1059}
1060
1061impl LosslessMetadata {
1064 pub fn raw_json(&self) -> serde_json::Result<serde_json::Value> {
1066 serde_json::from_str(&self.raw_metadata)
1067 }
1068}
1069
1070impl Serialize for LosslessMetadata {
1071 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1072 where
1073 S: Serializer,
1074 {
1075 serializer.serialize_str(&self.raw_metadata)
1076 }
1077}
1078
1079impl<'de> Deserialize<'de> for LosslessMetadata {
1080 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1081 where
1082 D: Deserializer<'de>,
1083 {
1084 struct LosslessMetadataVisitor;
1085
1086 impl<'de> Visitor<'de> for LosslessMetadataVisitor {
1087 type Value = LosslessMetadata;
1088
1089 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1090 write!(formatter, "metadata string")
1091 }
1092
1093 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1094 where
1095 E: serde::de::Error,
1096 {
1097 let metadata = serde_json::from_str(value).map_err(serde::de::Error::custom)?;
1098 let raw_metadata = value.to_string();
1099 Ok(LosslessMetadata { raw_metadata, metadata })
1100 }
1101 }
1102 deserializer.deserialize_str(LosslessMetadataVisitor)
1103 }
1104}
1105
1106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1108pub struct MetadataSettings {
1109 #[serde(default)]
1110 pub remappings: Vec<Remapping>,
1111 pub optimizer: Optimizer,
1112 #[serde(default, skip_serializing_if = "Option::is_none")]
1113 pub metadata: Option<SettingsMetadata>,
1114 #[serde(default, rename = "compilationTarget")]
1117 pub compilation_target: BTreeMap<String, String>,
1118 #[serde(default)]
1123 pub libraries: BTreeMap<String, String>,
1124 #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
1127 pub via_ir: Option<bool>,
1128}
1129
1130#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1132pub struct MetadataSources {
1133 #[serde(flatten)]
1134 pub inner: BTreeMap<String, MetadataSource>,
1135}
1136
1137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1138pub struct MetadataSource {
1139 pub keccak256: String,
1141 #[serde(default)]
1145 pub urls: Vec<String>,
1146 #[serde(default, skip_serializing_if = "Option::is_none")]
1148 pub content: Option<String>,
1149 pub license: Option<String>,
1151}
1152
1153#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1155#[serde(rename_all = "camelCase")]
1156pub struct ModelCheckerSettings {
1157 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1158 pub contracts: BTreeMap<String, Vec<String>>,
1159 #[serde(
1160 default,
1161 with = "serde_helpers::display_from_str_opt",
1162 skip_serializing_if = "Option::is_none"
1163 )]
1164 pub engine: Option<ModelCheckerEngine>,
1165 #[serde(skip_serializing_if = "Option::is_none")]
1166 pub timeout: Option<u32>,
1167 #[serde(skip_serializing_if = "Option::is_none")]
1168 pub targets: Option<Vec<ModelCheckerTarget>>,
1169 #[serde(skip_serializing_if = "Option::is_none")]
1170 pub invariants: Option<Vec<ModelCheckerInvariant>>,
1171 #[serde(skip_serializing_if = "Option::is_none")]
1172 pub show_unproved: Option<bool>,
1173 #[serde(skip_serializing_if = "Option::is_none")]
1174 pub div_mod_with_slacks: Option<bool>,
1175 #[serde(skip_serializing_if = "Option::is_none")]
1176 pub solvers: Option<Vec<ModelCheckerSolver>>,
1177 #[serde(skip_serializing_if = "Option::is_none")]
1178 pub show_unsupported: Option<bool>,
1179 #[serde(skip_serializing_if = "Option::is_none")]
1180 pub show_proved_safe: Option<bool>,
1181}
1182
1183#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1185pub enum ModelCheckerEngine {
1186 #[default]
1187 Default,
1188 All,
1189 BMC,
1190 CHC,
1191}
1192
1193impl fmt::Display for ModelCheckerEngine {
1194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1195 let string = match self {
1196 ModelCheckerEngine::Default => "none",
1197 ModelCheckerEngine::All => "all",
1198 ModelCheckerEngine::BMC => "bmc",
1199 ModelCheckerEngine::CHC => "chc",
1200 };
1201 write!(f, "{string}")
1202 }
1203}
1204
1205impl FromStr for ModelCheckerEngine {
1206 type Err = String;
1207
1208 fn from_str(s: &str) -> Result<Self, Self::Err> {
1209 match s {
1210 "none" => Ok(ModelCheckerEngine::Default),
1211 "all" => Ok(ModelCheckerEngine::All),
1212 "bmc" => Ok(ModelCheckerEngine::BMC),
1213 "chc" => Ok(ModelCheckerEngine::CHC),
1214 s => Err(format!("Unknown model checker engine: {s}")),
1215 }
1216 }
1217}
1218
1219#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1221#[serde(rename_all = "camelCase")]
1222pub enum ModelCheckerTarget {
1223 Assert,
1224 Underflow,
1225 Overflow,
1226 DivByZero,
1227 ConstantCondition,
1228 PopEmptyArray,
1229 OutOfBounds,
1230 Balance,
1231}
1232
1233impl fmt::Display for ModelCheckerTarget {
1234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1235 let string = match self {
1236 ModelCheckerTarget::Assert => "assert",
1237 ModelCheckerTarget::Underflow => "underflow",
1238 ModelCheckerTarget::Overflow => "overflow",
1239 ModelCheckerTarget::DivByZero => "divByZero",
1240 ModelCheckerTarget::ConstantCondition => "constantCondition",
1241 ModelCheckerTarget::PopEmptyArray => "popEmptyArray",
1242 ModelCheckerTarget::OutOfBounds => "outOfBounds",
1243 ModelCheckerTarget::Balance => "balance",
1244 };
1245 write!(f, "{string}")
1246 }
1247}
1248
1249impl FromStr for ModelCheckerTarget {
1250 type Err = String;
1251
1252 fn from_str(s: &str) -> Result<Self, Self::Err> {
1253 match s {
1254 "assert" => Ok(ModelCheckerTarget::Assert),
1255 "underflow" => Ok(ModelCheckerTarget::Underflow),
1256 "overflow" => Ok(ModelCheckerTarget::Overflow),
1257 "divByZero" => Ok(ModelCheckerTarget::DivByZero),
1258 "constantCondition" => Ok(ModelCheckerTarget::ConstantCondition),
1259 "popEmptyArray" => Ok(ModelCheckerTarget::PopEmptyArray),
1260 "outOfBounds" => Ok(ModelCheckerTarget::OutOfBounds),
1261 "balance" => Ok(ModelCheckerTarget::Balance),
1262 s => Err(format!("Unknown model checker target: {s}")),
1263 }
1264 }
1265}
1266
1267#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1269#[serde(rename_all = "camelCase")]
1270pub enum ModelCheckerInvariant {
1271 Contract,
1272 Reentrancy,
1273}
1274
1275impl fmt::Display for ModelCheckerInvariant {
1276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1277 let string = match self {
1278 ModelCheckerInvariant::Contract => "contract",
1279 ModelCheckerInvariant::Reentrancy => "reentrancy",
1280 };
1281 write!(f, "{string}")
1282 }
1283}
1284
1285impl FromStr for ModelCheckerInvariant {
1286 type Err = String;
1287
1288 fn from_str(s: &str) -> Result<Self, Self::Err> {
1289 match s {
1290 "contract" => Ok(ModelCheckerInvariant::Contract),
1291 "reentrancy" => Ok(ModelCheckerInvariant::Reentrancy),
1292 s => Err(format!("Unknown model checker invariant: {s}")),
1293 }
1294 }
1295}
1296
1297#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1299#[serde(rename_all = "camelCase")]
1300pub enum ModelCheckerSolver {
1301 Cvc4,
1302 Eld,
1303 Smtlib2,
1304 Z3,
1305}
1306
1307impl fmt::Display for ModelCheckerSolver {
1308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1309 let string = match self {
1310 ModelCheckerSolver::Cvc4 => "cvc4",
1311 ModelCheckerSolver::Eld => "eld",
1312 ModelCheckerSolver::Smtlib2 => "smtlib2",
1313 ModelCheckerSolver::Z3 => "z3",
1314 };
1315 write!(f, "{string}")
1316 }
1317}
1318
1319impl FromStr for ModelCheckerSolver {
1320 type Err = String;
1321
1322 fn from_str(s: &str) -> Result<Self, Self::Err> {
1323 match s {
1324 "cvc4" => Ok(ModelCheckerSolver::Cvc4),
1325 "eld" => Ok(ModelCheckerSolver::Cvc4),
1326 "smtlib2" => Ok(ModelCheckerSolver::Smtlib2),
1327 "z3" => Ok(ModelCheckerSolver::Z3),
1328 s => Err(format!("Unknown model checker invariant: {s}")),
1329 }
1330 }
1331}
1332
1333#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1334pub struct Compiler {
1335 pub version: String,
1336}
1337
1338#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1339pub struct Output {
1340 pub abi: Vec<SolcAbi>,
1341 pub devdoc: Option<Doc>,
1342 pub userdoc: Option<Doc>,
1343}
1344
1345#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1346pub struct SolcAbi {
1347 #[serde(default)]
1348 pub inputs: Vec<Item>,
1349 #[serde(rename = "stateMutability", skip_serializing_if = "Option::is_none")]
1350 pub state_mutability: Option<String>,
1351 #[serde(rename = "type")]
1352 pub abi_type: String,
1353 #[serde(default, skip_serializing_if = "Option::is_none")]
1354 pub name: Option<String>,
1355 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1356 pub outputs: Vec<Item>,
1357 #[serde(default, skip_serializing_if = "Option::is_none")]
1359 pub anonymous: Option<bool>,
1360}
1361
1362#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1363pub struct Item {
1364 #[serde(rename = "internalType")]
1365 pub internal_type: Option<String>,
1366 pub name: String,
1367 #[serde(rename = "type")]
1368 pub put_type: String,
1369 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1370 pub components: Vec<Item>,
1371 #[serde(default, skip_serializing_if = "Option::is_none")]
1373 pub indexed: Option<bool>,
1374}
1375
1376#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1377pub struct Doc {
1378 #[serde(default, skip_serializing_if = "Option::is_none")]
1379 pub kind: Option<String>,
1380 #[serde(default, skip_serializing_if = "Option::is_none")]
1381 pub methods: Option<DocLibraries>,
1382 #[serde(default, skip_serializing_if = "Option::is_none")]
1383 pub version: Option<u32>,
1384}
1385
1386#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
1387pub struct DocLibraries {
1388 #[serde(flatten)]
1389 pub libs: BTreeMap<String, serde_json::Value>,
1390}
1391
1392#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1396pub struct Source {
1397 pub content: Arc<String>,
1403}
1404
1405impl Source {
1406 pub fn new(content: impl Into<String>) -> Self {
1408 Self { content: Arc::new(content.into()) }
1409 }
1410
1411 pub fn read(file: impl AsRef<Path>) -> Result<Self, SolcIoError> {
1413 let file = file.as_ref();
1414 Ok(Self::new(fs::read_to_string(file).map_err(|err| SolcIoError::new(err, file))?))
1415 }
1416
1417 pub fn read_all_from(dir: impl AsRef<Path>) -> Result<Sources, SolcIoError> {
1419 Self::read_all_files(utils::source_files(dir))
1420 }
1421
1422 pub fn read_all_files(files: Vec<PathBuf>) -> Result<Sources, SolcIoError> {
1426 Self::read_all(files)
1427 }
1428
1429 pub fn read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
1431 where
1432 I: IntoIterator<Item = T>,
1433 T: Into<PathBuf>,
1434 {
1435 files
1436 .into_iter()
1437 .map(Into::into)
1438 .map(|file| Self::read(&file).map(|source| (file, source)))
1439 .collect()
1440 }
1441
1442 pub fn par_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
1447 where
1448 I: IntoIterator<Item = T>,
1449 <I as IntoIterator>::IntoIter: Send,
1450 T: Into<PathBuf> + Send,
1451 {
1452 use rayon::{iter::ParallelBridge, prelude::ParallelIterator};
1453 files
1454 .into_iter()
1455 .par_bridge()
1456 .map(Into::into)
1457 .map(|file| Self::read(&file).map(|source| (file, source)))
1458 .collect()
1459 }
1460
1461 pub fn content_hash(&self) -> String {
1463 let mut hasher = md5::Md5::new();
1464 hasher.update(self);
1465 let result = hasher.finalize();
1466 hex::encode(result)
1467 }
1468
1469 pub fn parse_imports(&self) -> Vec<&str> {
1471 utils::find_import_paths(self.as_ref()).map(|m| m.as_str()).collect()
1472 }
1473}
1474
1475#[cfg(feature = "async")]
1476impl Source {
1477 pub async fn async_read(file: impl AsRef<Path>) -> Result<Self, SolcIoError> {
1479 let file = file.as_ref();
1480 Ok(Self::new(
1481 tokio::fs::read_to_string(file).await.map_err(|err| SolcIoError::new(err, file))?,
1482 ))
1483 }
1484
1485 pub async fn async_read_all_from(dir: impl AsRef<Path>) -> Result<Sources, SolcIoError> {
1487 Self::async_read_all(utils::source_files(dir.as_ref())).await
1488 }
1489
1490 pub async fn async_read_all<T, I>(files: I) -> Result<Sources, SolcIoError>
1492 where
1493 I: IntoIterator<Item = T>,
1494 T: Into<PathBuf>,
1495 {
1496 futures_util::future::join_all(
1497 files
1498 .into_iter()
1499 .map(Into::into)
1500 .map(|file| async { Self::async_read(&file).await.map(|source| (file, source)) }),
1501 )
1502 .await
1503 .into_iter()
1504 .collect()
1505 }
1506}
1507
1508impl AsRef<str> for Source {
1509 fn as_ref(&self) -> &str {
1510 &self.content
1511 }
1512}
1513
1514impl AsRef<[u8]> for Source {
1515 fn as_ref(&self) -> &[u8] {
1516 self.content.as_bytes()
1517 }
1518}
1519
1520#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
1522pub struct CompilerOutput {
1523 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1524 pub errors: Vec<Error>,
1525 #[serde(default)]
1526 pub sources: BTreeMap<String, SourceFile>,
1527 #[serde(default)]
1528 pub contracts: Contracts,
1529}
1530
1531impl CompilerOutput {
1532 pub fn has_error(&self) -> bool {
1534 self.errors.iter().any(|err| err.severity.is_error())
1535 }
1536
1537 pub fn has_warning(&self, ignored_error_codes: &[u64]) -> bool {
1539 self.errors.iter().any(|err| {
1540 if err.severity.is_warning() {
1541 err.error_code.as_ref().map_or(false, |code| !ignored_error_codes.contains(code))
1542 } else {
1543 false
1544 }
1545 })
1546 }
1547
1548 pub fn find(&self, contract: impl AsRef<str>) -> Option<CompactContractRef> {
1550 let contract_name = contract.as_ref();
1551 self.contracts_iter().find_map(|(name, contract)| {
1552 (name == contract_name).then(|| CompactContractRef::from(contract))
1553 })
1554 }
1555
1556 pub fn remove(&mut self, contract: impl AsRef<str>) -> Option<Contract> {
1558 let contract_name = contract.as_ref();
1559 self.contracts.values_mut().find_map(|c| c.remove(contract_name))
1560 }
1561
1562 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1564 self.contracts.values().flatten()
1565 }
1566
1567 pub fn contracts_into_iter(self) -> impl Iterator<Item = (String, Contract)> {
1569 self.contracts.into_values().flatten()
1570 }
1571
1572 pub fn get(&self, path: &str, contract: &str) -> Option<CompactContractRef> {
1575 self.contracts
1576 .get(path)
1577 .and_then(|contracts| contracts.get(contract))
1578 .map(CompactContractRef::from)
1579 }
1580
1581 pub fn split(self) -> (SourceFiles, OutputContracts) {
1584 (SourceFiles(self.sources), OutputContracts(self.contracts))
1585 }
1586
1587 pub fn retain_files<'a, I>(&mut self, files: I)
1591 where
1592 I: IntoIterator<Item = &'a str>,
1593 {
1594 let files: HashSet<_> = files.into_iter().map(|s| s.to_lowercase()).collect();
1597 self.contracts.retain(|f, _| files.contains(f.to_lowercase().as_str()));
1598 self.sources.retain(|f, _| files.contains(f.to_lowercase().as_str()));
1599 }
1600
1601 pub fn merge(&mut self, other: CompilerOutput) {
1602 self.errors.extend(other.errors);
1603 self.contracts.extend(other.contracts);
1604 self.sources.extend(other.sources);
1605 }
1606}
1607
1608#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
1610pub struct OutputContracts(pub Contracts);
1611
1612impl OutputContracts {
1613 pub fn into_contracts(self) -> impl Iterator<Item = (String, Contract)> {
1615 self.0.into_values().flatten()
1616 }
1617
1618 pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
1620 self.0.values().flatten()
1621 }
1622
1623 pub fn find(&self, contract: impl AsRef<str>) -> Option<CompactContractRef> {
1625 let contract_name = contract.as_ref();
1626 self.contracts_iter().find_map(|(name, contract)| {
1627 (name == contract_name).then(|| CompactContractRef::from(contract))
1628 })
1629 }
1630
1631 pub fn remove(&mut self, contract: impl AsRef<str>) -> Option<Contract> {
1633 let contract_name = contract.as_ref();
1634 self.0.values_mut().find_map(|c| c.remove(contract_name))
1635 }
1636}
1637
1638#[derive(Clone, Debug, PartialEq)]
1647pub struct LosslessAbi {
1648 pub abi_value: serde_json::Value,
1650 pub abi: Abi,
1652}
1653
1654impl Default for LosslessAbi {
1655 fn default() -> Self {
1656 LosslessAbi { abi_value: serde_json::json!([]), abi: Default::default() }
1657 }
1658}
1659
1660impl From<LosslessAbi> for Abi {
1661 fn from(abi: LosslessAbi) -> Self {
1662 abi.abi
1663 }
1664}
1665
1666impl Serialize for LosslessAbi {
1667 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1668 where
1669 S: Serializer,
1670 {
1671 self.abi_value.serialize(serializer)
1672 }
1673}
1674
1675impl<'de> Deserialize<'de> for LosslessAbi {
1676 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1677 where
1678 D: Deserializer<'de>,
1679 {
1680 let abi_value = serde_json::Value::deserialize(deserializer)?;
1681 let abi = serde_json::from_value(abi_value.clone()).map_err(serde::de::Error::custom)?;
1682 Ok(Self { abi_value, abi })
1683 }
1684}
1685
1686#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1687pub struct UserDoc {
1688 #[serde(default, skip_serializing_if = "Option::is_none")]
1689 pub version: Option<u32>,
1690 #[serde(default, skip_serializing_if = "Option::is_none")]
1691 pub kind: Option<String>,
1692 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1693 pub methods: BTreeMap<String, UserDocNotice>,
1694 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1695 pub events: BTreeMap<String, UserDocNotice>,
1696 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1697 pub errors: BTreeMap<String, Vec<UserDocNotice>>,
1698 #[serde(default, skip_serializing_if = "Option::is_none")]
1699 pub notice: Option<String>,
1700}
1701
1702#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1703#[serde(untagged)]
1704pub enum UserDocNotice {
1705 Constructor(String),
1707 Notice { notice: String },
1708}
1709
1710#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1711pub struct DevDoc {
1712 #[serde(default, skip_serializing_if = "Option::is_none")]
1713 pub version: Option<u32>,
1714 #[serde(default, skip_serializing_if = "Option::is_none")]
1715 pub kind: Option<String>,
1716 #[serde(default, skip_serializing_if = "Option::is_none")]
1717 pub author: Option<String>,
1718 #[serde(default, skip_serializing_if = "Option::is_none")]
1719 pub details: Option<String>,
1720 #[serde(default, rename = "custom:experimental", skip_serializing_if = "Option::is_none")]
1721 pub custom_experimental: Option<String>,
1722 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1723 pub methods: BTreeMap<String, MethodDoc>,
1724 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1725 pub events: BTreeMap<String, EventDoc>,
1726 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1727 pub errors: BTreeMap<String, Vec<ErrorDoc>>,
1728 #[serde(default, skip_serializing_if = "Option::is_none")]
1729 pub title: Option<String>,
1730}
1731
1732#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1733pub struct MethodDoc {
1734 #[serde(default, skip_serializing_if = "Option::is_none")]
1735 pub details: Option<String>,
1736 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1737 pub params: BTreeMap<String, String>,
1738 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1739 pub returns: BTreeMap<String, String>,
1740}
1741
1742#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1743pub struct EventDoc {
1744 #[serde(default, skip_serializing_if = "Option::is_none")]
1745 pub details: Option<String>,
1746 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1747 pub params: BTreeMap<String, String>,
1748}
1749
1750#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1751pub struct ErrorDoc {
1752 #[serde(default, skip_serializing_if = "Option::is_none")]
1753 pub details: Option<String>,
1754 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1755 pub params: BTreeMap<String, String>,
1756}
1757
1758#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1759#[serde(rename_all = "camelCase")]
1760pub struct Evm {
1761 #[serde(default, skip_serializing_if = "Option::is_none")]
1762 pub assembly: Option<String>,
1763 #[serde(default, skip_serializing_if = "Option::is_none")]
1764 pub legacy_assembly: Option<serde_json::Value>,
1765 pub bytecode: Option<Bytecode>,
1766 #[serde(default, skip_serializing_if = "Option::is_none")]
1767 pub deployed_bytecode: Option<DeployedBytecode>,
1768 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1770 pub method_identifiers: BTreeMap<String, String>,
1771 #[serde(default, skip_serializing_if = "Option::is_none")]
1773 pub gas_estimates: Option<GasEstimates>,
1774}
1775
1776impl Evm {
1777 pub(crate) fn into_compact(self) -> CompactEvm {
1780 let Evm {
1781 assembly,
1782 legacy_assembly,
1783 bytecode,
1784 deployed_bytecode,
1785 method_identifiers,
1786 gas_estimates,
1787 } = self;
1788
1789 let (bytecode, deployed_bytecode) = match (bytecode, deployed_bytecode) {
1790 (Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())),
1791 (None, Some(dbcode)) => (None, Some(dbcode.into())),
1792 (Some(bcode), None) => (Some(bcode.into()), None),
1793 (None, None) => (None, None),
1794 };
1795
1796 CompactEvm {
1797 assembly,
1798 legacy_assembly,
1799 bytecode,
1800 deployed_bytecode,
1801 method_identifiers,
1802 gas_estimates,
1803 }
1804 }
1805}
1806
1807#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1808#[serde(rename_all = "camelCase")]
1809pub(crate) struct CompactEvm {
1810 #[serde(default, skip_serializing_if = "Option::is_none")]
1811 pub assembly: Option<String>,
1812 #[serde(default, skip_serializing_if = "Option::is_none")]
1813 pub legacy_assembly: Option<serde_json::Value>,
1814 pub bytecode: Option<CompactBytecode>,
1815 #[serde(default, skip_serializing_if = "Option::is_none")]
1816 pub deployed_bytecode: Option<CompactDeployedBytecode>,
1817 #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
1819 pub method_identifiers: BTreeMap<String, String>,
1820 #[serde(default, skip_serializing_if = "Option::is_none")]
1822 pub gas_estimates: Option<GasEstimates>,
1823}
1824
1825#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1826#[serde(rename_all = "camelCase")]
1827pub struct FunctionDebugData {
1828 pub entry_point: Option<u32>,
1829 pub id: Option<u32>,
1830 pub parameter_slots: Option<u32>,
1831 pub return_slots: Option<u32>,
1832}
1833
1834#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1835pub struct GeneratedSource {
1836 pub ast: serde_json::Value,
1837 pub contents: String,
1838 pub id: u32,
1839 pub language: String,
1840 pub name: String,
1841}
1842
1843#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1846pub struct Offsets {
1847 pub start: u32,
1848 pub length: u32,
1849}
1850
1851#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1852pub struct GasEstimates {
1853 pub creation: Creation,
1854 #[serde(default)]
1855 pub external: BTreeMap<String, String>,
1856 #[serde(default)]
1857 pub internal: BTreeMap<String, String>,
1858}
1859
1860#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1861#[serde(rename_all = "camelCase")]
1862pub struct Creation {
1863 pub code_deposit_cost: String,
1864 pub execution_cost: String,
1865 pub total_cost: String,
1866}
1867
1868#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1869pub struct Ewasm {
1870 #[serde(default, skip_serializing_if = "Option::is_none")]
1871 pub wast: Option<String>,
1872 pub wasm: String,
1873}
1874
1875#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
1877pub struct StorageLayout {
1878 pub storage: Vec<Storage>,
1879 #[serde(default, deserialize_with = "serde_helpers::default_for_null")]
1880 pub types: BTreeMap<String, StorageType>,
1881}
1882
1883impl StorageLayout {
1884 fn is_empty(&self) -> bool {
1885 self.storage.is_empty() && self.types.is_empty()
1886 }
1887}
1888
1889#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1890pub struct Storage {
1891 #[serde(rename = "astId")]
1892 pub ast_id: u64,
1893 pub contract: String,
1894 pub label: String,
1895 pub offset: i64,
1896 pub slot: String,
1897 #[serde(rename = "type")]
1898 pub storage_type: String,
1899}
1900
1901#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
1902pub struct StorageType {
1903 pub encoding: String,
1904 #[serde(default, skip_serializing_if = "Option::is_none")]
1905 pub key: Option<String>,
1906 pub label: String,
1907 #[serde(rename = "numberOfBytes")]
1908 pub number_of_bytes: String,
1909 #[serde(default, skip_serializing_if = "Option::is_none")]
1910 pub value: Option<String>,
1911 #[serde(flatten)]
1913 pub other: BTreeMap<String, serde_json::Value>,
1914}
1915
1916#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
1917#[serde(rename_all = "camelCase")]
1918pub struct Error {
1919 #[serde(default, skip_serializing_if = "Option::is_none")]
1920 pub source_location: Option<SourceLocation>,
1921 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1922 pub secondary_source_locations: Vec<SecondarySourceLocation>,
1923 pub r#type: String,
1924 pub component: String,
1925 pub severity: Severity,
1926 #[serde(default, with = "serde_helpers::display_from_str_opt")]
1927 pub error_code: Option<u64>,
1928 pub message: String,
1929 pub formatted_message: Option<String>,
1930}
1931
1932impl fmt::Display for Error {
1936 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1937 if !Paint::is_enabled() {
1938 let msg = self.formatted_message.as_ref().unwrap_or(&self.message);
1939 self.fmt_severity(f)?;
1940 f.write_str(": ")?;
1941 return f.write_str(msg)
1942 }
1943
1944 styled(f, self.severity.color().style().bold(), |f| self.fmt_severity(f))?;
1946 fmt_msg(f, &self.message)?;
1947
1948 if let Some(msg) = &self.formatted_message {
1949 let mut lines = msg.lines();
1950
1951 lines.next();
1953
1954 fmt_source_location(f, &mut lines)?;
1956
1957 while let Some(line) = lines.next() {
1959 f.write_str("\n")?;
1960
1961 if let Some((note, msg)) = line.split_once(':') {
1962 styled(f, Self::secondary_style(), |f| f.write_str(note))?;
1963 fmt_msg(f, msg)?;
1964 } else {
1965 f.write_str(line)?;
1966 }
1967
1968 fmt_source_location(f, &mut lines)?;
1969 }
1970 }
1971
1972 Ok(())
1973 }
1974}
1975
1976impl Error {
1977 pub fn error_style(&self) -> Style {
1979 self.severity.color().style().bold()
1980 }
1981
1982 pub fn message_style() -> Style {
1984 Color::White.style().bold()
1985 }
1986
1987 pub fn secondary_style() -> Style {
1989 Color::Cyan.style().bold()
1990 }
1991
1992 pub fn highlight_style() -> Style {
1994 Color::Yellow.style()
1995 }
1996
1997 pub fn diag_style() -> Style {
1999 Color::Yellow.style().bold()
2000 }
2001
2002 pub fn frame_style() -> Style {
2004 Color::Blue.style()
2005 }
2006
2007 fn fmt_severity(&self, f: &mut fmt::Formatter) -> fmt::Result {
2013 f.write_str(self.severity.as_str())?;
2014 if let Some(code) = self.error_code {
2015 write!(f, " ({code})")?;
2016 }
2017 Ok(())
2018 }
2019}
2020
2021fn styled<F>(f: &mut fmt::Formatter, style: Style, fun: F) -> fmt::Result
2023where
2024 F: FnOnce(&mut fmt::Formatter) -> fmt::Result,
2025{
2026 style.fmt_prefix(f)?;
2027 fun(f)?;
2028 style.fmt_suffix(f)
2029}
2030
2031fn fmt_msg(f: &mut fmt::Formatter, msg: &str) -> fmt::Result {
2033 styled(f, Error::message_style(), |f| {
2034 f.write_str(": ")?;
2035 f.write_str(msg.trim_start())
2036 })
2037}
2038
2039fn fmt_source_location(f: &mut fmt::Formatter, lines: &mut std::str::Lines) -> fmt::Result {
2048 if let Some(line) = lines.next() {
2050 f.write_str("\n")?;
2051
2052 let arrow = "-->";
2053 if let Some((left, loc)) = line.split_once(arrow) {
2054 f.write_str(left)?;
2055 styled(f, Error::frame_style(), |f| f.write_str(arrow))?;
2056 f.write_str(loc)?;
2057 } else {
2058 f.write_str(line)?;
2059 }
2060 }
2061
2062 let next_3 = lines.take(3).collect::<Vec<_>>();
2065 let [line1, line2, line3] = next_3[..] else {
2066 for line in next_3 {
2067 f.write_str("\n")?;
2068 f.write_str(line)?;
2069 }
2070 return Ok(())
2071 };
2072
2073 fmt_framed_location(f, line1, None)?;
2075
2076 let hl_start = line3.find('^');
2078 let highlight = hl_start.map(|start| {
2079 let end = if line3.contains("^ (") {
2080 line2.len()
2082 } else if let Some(carets) = line3[start..].find(|c: char| c != '^') {
2083 start + carets
2085 } else {
2086 line3.len()
2088 }
2089 .min(line2.len());
2091 (start.min(end)..end, Error::highlight_style())
2092 });
2093 fmt_framed_location(f, line2, highlight)?;
2094
2095 let highlight = hl_start.map(|i| (i..line3.len(), Error::diag_style()));
2097 fmt_framed_location(f, line3, highlight)
2098}
2099
2100fn fmt_framed_location(
2102 f: &mut fmt::Formatter,
2103 line: &str,
2104 highlight: Option<(Range<usize>, Style)>,
2105) -> fmt::Result {
2106 f.write_str("\n")?;
2107
2108 if let Some((space_or_line_number, rest)) = line.split_once('|') {
2109 if !space_or_line_number.chars().all(|c| c.is_whitespace() || c.is_numeric()) {
2111 return f.write_str(line)
2112 }
2113
2114 styled(f, Error::frame_style(), |f| {
2115 f.write_str(space_or_line_number)?;
2116 f.write_str("|")
2117 })?;
2118
2119 if let Some((range, style)) = highlight {
2120 let Range { start, end } = range;
2121 let rest_start = line.len() - rest.len();
2122 f.write_str(&line[rest_start..start])?;
2123 styled(f, style, |f| f.write_str(&line[range]))?;
2124 f.write_str(&line[end..])
2125 } else {
2126 f.write_str(rest)
2127 }
2128 } else {
2129 f.write_str(line)
2130 }
2131}
2132
2133#[derive(
2134 Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
2135)]
2136#[serde(rename_all = "lowercase")]
2137pub enum Severity {
2138 #[default]
2139 Error,
2140 Warning,
2141 Info,
2142}
2143
2144impl fmt::Display for Severity {
2145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2146 f.write_str(self.as_str())
2147 }
2148}
2149
2150impl FromStr for Severity {
2151 type Err = String;
2152
2153 fn from_str(s: &str) -> Result<Self, Self::Err> {
2154 match s {
2155 "Error" | "error" => Ok(Self::Error),
2156 "Warning" | "warning" => Ok(Self::Warning),
2157 "Info" | "info" => Ok(Self::Info),
2158 s => Err(format!("Invalid severity: {s}")),
2159 }
2160 }
2161}
2162
2163impl Severity {
2164 pub const fn is_error(&self) -> bool {
2166 matches!(self, Self::Error)
2167 }
2168
2169 pub const fn is_warning(&self) -> bool {
2171 matches!(self, Self::Warning)
2172 }
2173
2174 pub const fn is_info(&self) -> bool {
2176 matches!(self, Self::Info)
2177 }
2178
2179 pub const fn as_str(&self) -> &'static str {
2181 match self {
2182 Self::Error => "Error",
2183 Self::Warning => "Warning",
2184 Self::Info => "Info",
2185 }
2186 }
2187
2188 pub const fn color(&self) -> Color {
2190 match self {
2191 Self::Error => Color::Red,
2192 Self::Warning => Color::Yellow,
2193 Self::Info => Color::White,
2194 }
2195 }
2196}
2197
2198#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
2199pub struct SourceLocation {
2200 pub file: String,
2201 pub start: i32,
2202 pub end: i32,
2203}
2204
2205#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
2206pub struct SecondarySourceLocation {
2207 pub file: Option<String>,
2208 pub start: Option<i32>,
2209 pub end: Option<i32>,
2210 pub message: Option<String>,
2211}
2212
2213#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
2214pub struct SourceFile {
2215 pub id: u32,
2216 #[serde(default, with = "serde_helpers::empty_json_object_opt")]
2217 pub ast: Option<Ast>,
2218}
2219
2220impl SourceFile {
2223 pub fn contains_contract_definition(&self) -> bool {
2226 if let Some(ref ast) = self.ast {
2227 return ast.nodes.iter().any(|node| node.node_type == NodeType::ContractDefinition)
2229 }
2231
2232 false
2233 }
2234}
2235
2236#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
2239pub struct SourceFiles(pub BTreeMap<String, SourceFile>);
2240
2241impl SourceFiles {
2242 pub fn into_ids(self) -> impl Iterator<Item = (u32, String)> {
2252 self.0.into_iter().map(|(k, v)| (v.id, k))
2253 }
2254
2255 pub fn into_paths(self) -> impl Iterator<Item = (String, u32)> {
2265 self.0.into_iter().map(|(k, v)| (k, v.id))
2266 }
2267}
2268
2269#[cfg(test)]
2270mod tests {
2271 use super::*;
2272 use crate::AggregatedCompilerOutput;
2273 use ethers_core::types::Address;
2274
2275 #[test]
2276 fn can_parse_declaration_error() {
2277 let s = r#"{
2278 "errors": [
2279 {
2280 "component": "general",
2281 "errorCode": "7576",
2282 "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"revert\"?\n --> /Users/src/utils/UpgradeProxy.sol:35:17:\n |\n35 | refert(\"Transparent ERC1967 proxies do not have upgradeable implementations\");\n | ^^^^^^\n\n",
2283 "message": "Undeclared identifier. Did you mean \"revert\"?",
2284 "severity": "error",
2285 "sourceLocation": {
2286 "end": 1623,
2287 "file": "/Users/src/utils/UpgradeProxy.sol",
2288 "start": 1617
2289 },
2290 "type": "DeclarationError"
2291 }
2292 ],
2293 "sources": { }
2294}"#;
2295
2296 let out: CompilerOutput = serde_json::from_str(s).unwrap();
2297 assert_eq!(out.errors.len(), 1);
2298
2299 let mut aggregated = AggregatedCompilerOutput::default();
2300 aggregated.extend("0.8.12".parse().unwrap(), out);
2301 assert!(!aggregated.is_unchanged());
2302 }
2303
2304 #[test]
2305 fn can_link_bytecode() {
2306 #[derive(Serialize, Deserialize)]
2309 struct Mockject {
2310 object: BytecodeObject,
2311 }
2312 fn parse_bytecode(bytecode: &str) -> BytecodeObject {
2313 let object: Mockject =
2314 serde_json::from_value(serde_json::json!({ "object": bytecode })).unwrap();
2315 object.object
2316 }
2317
2318 let bytecode = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__lib2.sol:L____________________________6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
2319
2320 let mut object = parse_bytecode(bytecode);
2321 assert!(object.is_unlinked());
2322 assert!(object.contains_placeholder("lib2.sol", "L"));
2323 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
2324 assert!(object.link("lib2.sol", "L", Address::random()).resolve().is_some());
2325 assert!(!object.is_unlinked());
2326
2327 let mut code = Bytecode {
2328 function_debug_data: Default::default(),
2329 object: parse_bytecode(bytecode),
2330 opcodes: None,
2331 source_map: None,
2332 generated_sources: vec![],
2333 link_references: BTreeMap::from([(
2334 "lib2.sol".to_string(),
2335 BTreeMap::from([("L".to_string(), vec![])]),
2336 )]),
2337 };
2338
2339 assert!(!code.link("lib2.sol", "Y", Address::random()));
2340 assert!(code.link("lib2.sol", "L", Address::random()));
2341 assert!(code.link("lib2.sol", "L", Address::random()));
2342
2343 let hashed_placeholder = "6060604052341561000f57600080fd5b60f48061001d6000396000f300606060405260043610603e5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166326121ff081146043575b600080fd5b3415604d57600080fd5b60536055565b005b73__$cb901161e812ceb78cfe30ca65050c4337$__6326121ff06040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160006040518083038186803b151560b357600080fd5b6102c65a03f4151560c357600080fd5b5050505600a165627a7a723058207979b30bd4a07c77b02774a511f2a1dd04d7e5d65b5c2735b5fc96ad61d43ae40029";
2344 let mut object = parse_bytecode(hashed_placeholder);
2345 assert!(object.is_unlinked());
2346 assert!(object.contains_placeholder("lib2.sol", "L"));
2347 assert!(object.contains_fully_qualified_placeholder("lib2.sol:L"));
2348 assert!(object.link("lib2.sol", "L", Address::default()).resolve().is_some());
2349 assert!(!object.is_unlinked());
2350 }
2351
2352 #[test]
2353 fn can_parse_compiler_output() {
2354 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2355 dir.push("test-data/out");
2356
2357 for path in fs::read_dir(dir).unwrap() {
2358 let path = path.unwrap().path();
2359 let compiler_output = fs::read_to_string(&path).unwrap();
2360 serde_json::from_str::<CompilerOutput>(&compiler_output).unwrap_or_else(|err| {
2361 panic!("Failed to read compiler output of {} {}", path.display(), err)
2362 });
2363 }
2364 }
2365
2366 #[test]
2367 fn can_parse_compiler_input() {
2368 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2369 dir.push("test-data/in");
2370
2371 for path in fs::read_dir(dir).unwrap() {
2372 let path = path.unwrap().path();
2373 let compiler_input = fs::read_to_string(&path).unwrap();
2374 serde_json::from_str::<CompilerInput>(&compiler_input).unwrap_or_else(|err| {
2375 panic!("Failed to read compiler input of {} {}", path.display(), err)
2376 });
2377 }
2378 }
2379
2380 #[test]
2381 fn can_parse_standard_json_compiler_input() {
2382 let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2383 dir.push("test-data/in");
2384
2385 for path in fs::read_dir(dir).unwrap() {
2386 let path = path.unwrap().path();
2387 let compiler_input = fs::read_to_string(&path).unwrap();
2388 let val = serde_json::from_str::<StandardJsonCompilerInput>(&compiler_input)
2389 .unwrap_or_else(|err| {
2390 panic!("Failed to read compiler output of {} {}", path.display(), err)
2391 });
2392
2393 let pretty = serde_json::to_string_pretty(&val).unwrap();
2394 serde_json::from_str::<CompilerInput>(&pretty).unwrap_or_else(|err| {
2395 panic!("Failed to read converted compiler input of {} {}", path.display(), err)
2396 });
2397 }
2398 }
2399
2400 #[test]
2401 fn test_evm_version_normalization() {
2402 for (solc_version, evm_version, expected) in &[
2403 ("0.4.20", EvmVersion::Homestead, None),
2405 ("0.4.21", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2407 ("0.4.21", EvmVersion::Constantinople, Some(EvmVersion::Byzantium)),
2408 ("0.4.21", EvmVersion::London, Some(EvmVersion::Byzantium)),
2409 ("0.4.22", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2411 ("0.4.22", EvmVersion::Constantinople, Some(EvmVersion::Constantinople)),
2412 ("0.4.22", EvmVersion::London, Some(EvmVersion::Constantinople)),
2413 ("0.5.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2415 ("0.5.5", EvmVersion::Petersburg, Some(EvmVersion::Petersburg)),
2416 ("0.5.5", EvmVersion::London, Some(EvmVersion::Petersburg)),
2417 ("0.5.14", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2419 ("0.5.14", EvmVersion::Istanbul, Some(EvmVersion::Istanbul)),
2420 ("0.5.14", EvmVersion::London, Some(EvmVersion::Istanbul)),
2421 ("0.8.5", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2423 ("0.8.5", EvmVersion::Berlin, Some(EvmVersion::Berlin)),
2424 ("0.8.5", EvmVersion::London, Some(EvmVersion::Berlin)),
2425 ("0.8.7", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2427 ("0.8.7", EvmVersion::London, Some(EvmVersion::London)),
2428 ("0.8.7", EvmVersion::Paris, Some(EvmVersion::London)),
2429 ("0.8.18", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2431 ("0.8.18", EvmVersion::Paris, Some(EvmVersion::Paris)),
2432 ("0.8.18", EvmVersion::Shanghai, Some(EvmVersion::Paris)),
2433 ("0.8.20", EvmVersion::Homestead, Some(EvmVersion::Homestead)),
2435 ("0.8.20", EvmVersion::Paris, Some(EvmVersion::Paris)),
2436 ("0.8.20", EvmVersion::Shanghai, Some(EvmVersion::Shanghai)),
2437 ] {
2438 let version = Version::from_str(solc_version).unwrap();
2439 assert_eq!(
2440 &evm_version.normalize_version(&version),
2441 expected,
2442 "({version}, {evm_version:?})"
2443 )
2444 }
2445 }
2446
2447 #[test]
2448 fn can_sanitize_byte_code_hash() {
2449 let version: Version = "0.6.0".parse().unwrap();
2450
2451 let settings = Settings { metadata: Some(BytecodeHash::Ipfs.into()), ..Default::default() };
2452
2453 let input = CompilerInput {
2454 language: "Solidity".to_string(),
2455 sources: Default::default(),
2456 settings,
2457 };
2458
2459 let i = input.clone().sanitized(&version);
2460 assert_eq!(i.settings.metadata.unwrap().bytecode_hash, Some(BytecodeHash::Ipfs));
2461
2462 let version: Version = "0.5.17".parse().unwrap();
2463 let i = input.sanitized(&version);
2464 assert!(i.settings.metadata.unwrap().bytecode_hash.is_none());
2465 }
2466
2467 #[test]
2468 fn can_sanitize_cbor_metadata() {
2469 let version: Version = "0.8.18".parse().unwrap();
2470
2471 let settings = Settings {
2472 metadata: Some(SettingsMetadata::new(BytecodeHash::Ipfs, true)),
2473 ..Default::default()
2474 };
2475
2476 let input = CompilerInput {
2477 language: "Solidity".to_string(),
2478 sources: Default::default(),
2479 settings,
2480 };
2481
2482 let i = input.clone().sanitized(&version);
2483 assert_eq!(i.settings.metadata.unwrap().cbor_metadata, Some(true));
2484
2485 let i = input.sanitized(&Version::new(0, 8, 0));
2486 assert!(i.settings.metadata.unwrap().cbor_metadata.is_none());
2487 }
2488
2489 #[test]
2490 fn can_parse_libraries() {
2491 let libraries = ["./src/lib/LibraryContract.sol:Library:0xaddress".to_string()];
2492
2493 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2494
2495 assert_eq!(
2496 libs,
2497 BTreeMap::from([(
2498 PathBuf::from("./src/lib/LibraryContract.sol"),
2499 BTreeMap::from([("Library".to_string(), "0xaddress".to_string())])
2500 )])
2501 );
2502 }
2503
2504 #[test]
2505 fn can_parse_many_libraries() {
2506 let libraries= [
2507 "./src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2508 "./src/SizeAuction.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2509 "./src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2510 "./src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string(),
2511 "./src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string(),
2512 ];
2513
2514 let libs = Libraries::parse(&libraries[..]).unwrap().libs;
2515
2516 pretty_assertions::assert_eq!(
2517 libs,
2518 BTreeMap::from([
2519 (
2520 PathBuf::from("./src/SizeAuctionDiscount.sol"),
2521 BTreeMap::from([
2522 (
2523 "Chainlink".to_string(),
2524 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2525 ),
2526 (
2527 "Math".to_string(),
2528 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2529 )
2530 ])
2531 ),
2532 (
2533 PathBuf::from("./src/SizeAuction.sol"),
2534 BTreeMap::from([
2535 (
2536 "ChainlinkTWAP".to_string(),
2537 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2538 ),
2539 (
2540 "Math".to_string(),
2541 "0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c".to_string()
2542 )
2543 ])
2544 ),
2545 (
2546 PathBuf::from("./src/test/ChainlinkTWAP.t.sol"),
2547 BTreeMap::from([(
2548 "ChainlinkTWAP".to_string(),
2549 "0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5".to_string()
2550 )])
2551 ),
2552 ])
2553 );
2554 }
2555
2556 #[test]
2557 fn test_lossless_metadata() {
2558 #[derive(Debug, Serialize, Deserialize)]
2559 #[serde(rename_all = "camelCase")]
2560 pub struct Contract {
2561 #[serde(
2562 default,
2563 skip_serializing_if = "Option::is_none",
2564 with = "serde_helpers::json_string_opt"
2565 )]
2566 pub metadata: Option<LosslessMetadata>,
2567 }
2568
2569 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}"}"#;
2570
2571 let value: serde_json::Value = serde_json::from_str(s).unwrap();
2572 let c: Contract = serde_json::from_value(value).unwrap();
2573 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}");
2574
2575 let value = serde_json::to_string(&c).unwrap();
2576 pretty_assertions::assert_eq!(s, value);
2577 }
2578
2579 #[test]
2580 fn test_lossless_storage_layout() {
2581 let input = include_str!("../../test-data/foundryissue2462.json").trim();
2582 let layout: StorageLayout = serde_json::from_str(input).unwrap();
2583 pretty_assertions::assert_eq!(input, &serde_json::to_string(&layout).unwrap());
2584 }
2585
2586 #[test]
2588 fn can_parse_compiler_output_spells_0_6_12() {
2589 let path =
2590 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/0.6.12-with-libs.json");
2591 let content = fs::read_to_string(path).unwrap();
2592 let _output: CompilerOutput = serde_json::from_str(&content).unwrap();
2593 }
2594}