1use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
4use std::{collections::BTreeMap, fmt, str::FromStr};
5
6pub type FileOutputSelection = BTreeMap<String, Vec<String>>;
8
9#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
55#[serde(transparent)]
56pub struct OutputSelection(pub BTreeMap<String, FileOutputSelection>);
57
58impl OutputSelection {
59 pub fn complete_output_selection() -> Self {
63 BTreeMap::from([(
64 "*".to_string(),
65 BTreeMap::from([
66 ("*".to_string(), vec!["*".to_string()]),
67 (String::new(), vec!["*".to_string()]),
68 ]),
69 )])
70 .into()
71 }
72
73 pub fn default_output_selection() -> Self {
75 BTreeMap::from([("*".to_string(), Self::default_file_output_selection())]).into()
76 }
77
78 pub fn default_file_output_selection() -> FileOutputSelection {
82 BTreeMap::from([(
83 "*".to_string(),
84 ContractOutputSelection::basic().iter().map(ToString::to_string).collect(),
85 )])
86 }
87
88 pub fn common_output_selection(outputs: impl IntoIterator<Item = String>) -> Self {
91 BTreeMap::from([(
92 "*".to_string(),
93 BTreeMap::from([("*".to_string(), outputs.into_iter().collect())]),
94 )])
95 .into()
96 }
97
98 pub fn empty_file_output_select() -> FileOutputSelection {
100 Default::default()
101 }
102
103 pub fn ast_output_selection() -> Self {
105 BTreeMap::from([(
106 "*".to_string(),
107 BTreeMap::from([
108 ("*".to_string(), vec![]),
110 (String::new(), vec!["ast".to_string()]),
112 ]),
113 )])
114 .into()
115 }
116
117 pub fn is_subset_of(&self, other: &Self) -> bool {
120 self.0.iter().all(|(file, selection)| {
121 other.0.get(file).is_some_and(|other_selection| {
122 selection.iter().all(|(contract, outputs)| {
123 other_selection.get(contract).is_some_and(|other_outputs| {
124 outputs.iter().all(|output| other_outputs.contains(output))
125 })
126 })
127 })
128 })
129 }
130
131 pub fn contains_ast(&self) -> bool {
138 self.0.values().any(|file_selection| {
139 file_selection.iter().any(|(contract_name, outputs)| {
140 contract_name.is_empty() && outputs.iter().any(|o| o == "ast" || o == "*")
145 })
146 })
147 }
148}
149
150impl Serialize for OutputSelection {
154 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155 where
156 S: Serializer,
157 {
158 struct EmptyFileOutput;
159
160 impl Serialize for EmptyFileOutput {
161 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162 where
163 S: Serializer,
164 {
165 let mut map = serializer.serialize_map(Some(1))?;
166 map.serialize_entry("*", &[] as &[String])?;
167 map.end()
168 }
169 }
170
171 let mut map = serializer.serialize_map(Some(self.0.len()))?;
172 for (file, selection) in self.0.iter() {
173 if selection.is_empty() {
174 map.serialize_entry(file, &EmptyFileOutput {})?;
175 } else {
176 map.serialize_entry(file, selection)?;
177 }
178 }
179 map.end()
180 }
181}
182
183impl AsRef<BTreeMap<String, FileOutputSelection>> for OutputSelection {
184 fn as_ref(&self) -> &BTreeMap<String, FileOutputSelection> {
185 &self.0
186 }
187}
188
189impl AsMut<BTreeMap<String, FileOutputSelection>> for OutputSelection {
190 fn as_mut(&mut self) -> &mut BTreeMap<String, FileOutputSelection> {
191 &mut self.0
192 }
193}
194
195impl From<BTreeMap<String, FileOutputSelection>> for OutputSelection {
196 fn from(s: BTreeMap<String, FileOutputSelection>) -> Self {
197 Self(s)
198 }
199}
200
201#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
203pub enum ContractOutputSelection {
204 Abi,
205 DevDoc,
206 UserDoc,
207 Metadata,
208 Ir,
209 IrOptimized,
210 IrOptimizedAst,
211 StorageLayout,
212 TransientStorageLayout,
213 Evm(EvmOutputSelection),
214 Ewasm(EwasmOutputSelection),
215}
216
217impl ContractOutputSelection {
218 pub fn basic() -> Vec<Self> {
224 vec![
227 Self::Abi,
228 BytecodeOutputSelection::Object.into(),
230 BytecodeOutputSelection::SourceMap.into(),
231 BytecodeOutputSelection::LinkReferences.into(),
232 DeployedBytecodeOutputSelection::Object.into(),
234 DeployedBytecodeOutputSelection::SourceMap.into(),
235 DeployedBytecodeOutputSelection::LinkReferences.into(),
236 DeployedBytecodeOutputSelection::ImmutableReferences.into(),
237 EvmOutputSelection::MethodIdentifiers.into(),
238 ]
239 }
240}
241
242impl Serialize for ContractOutputSelection {
243 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244 where
245 S: Serializer,
246 {
247 serializer.collect_str(self)
248 }
249}
250
251impl<'de> Deserialize<'de> for ContractOutputSelection {
252 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
253 where
254 D: Deserializer<'de>,
255 {
256 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
257 }
258}
259
260impl fmt::Display for ContractOutputSelection {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 match self {
263 Self::Abi => f.write_str("abi"),
264 Self::DevDoc => f.write_str("devdoc"),
265 Self::UserDoc => f.write_str("userdoc"),
266 Self::Metadata => f.write_str("metadata"),
267 Self::Ir => f.write_str("ir"),
268 Self::IrOptimized => f.write_str("irOptimized"),
269 Self::IrOptimizedAst => f.write_str("irOptimizedAst"),
270 Self::StorageLayout => f.write_str("storageLayout"),
271 Self::TransientStorageLayout => f.write_str("transientStorageLayout"),
272 Self::Evm(e) => e.fmt(f),
273 Self::Ewasm(e) => e.fmt(f),
274 }
275 }
276}
277
278impl FromStr for ContractOutputSelection {
279 type Err = String;
280
281 fn from_str(s: &str) -> Result<Self, Self::Err> {
282 match s {
283 "abi" => Ok(Self::Abi),
284 "devdoc" => Ok(Self::DevDoc),
285 "userdoc" => Ok(Self::UserDoc),
286 "metadata" => Ok(Self::Metadata),
287 "ir" => Ok(Self::Ir),
288 "ir-optimized" | "irOptimized" | "iroptimized" => Ok(Self::IrOptimized),
289 "irOptimizedAst" | "ir-optimized-ast" | "iroptimizedast" => Ok(Self::IrOptimizedAst),
290 "storage-layout" | "storagelayout" | "storageLayout" => Ok(Self::StorageLayout),
291 "transient-storage-layout" | "transientstoragelayout" | "transientStorageLayout" => {
292 Ok(Self::TransientStorageLayout)
293 }
294 s => EvmOutputSelection::from_str(s)
295 .map(ContractOutputSelection::Evm)
296 .or_else(|_| EwasmOutputSelection::from_str(s).map(ContractOutputSelection::Ewasm))
297 .map_err(|_| format!("Invalid contract output selection: {s}")),
298 }
299 }
300}
301
302impl<T: Into<EvmOutputSelection>> From<T> for ContractOutputSelection {
303 fn from(evm: T) -> Self {
304 Self::Evm(evm.into())
305 }
306}
307
308impl From<EwasmOutputSelection> for ContractOutputSelection {
309 fn from(ewasm: EwasmOutputSelection) -> Self {
310 Self::Ewasm(ewasm)
311 }
312}
313
314#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
316pub enum EvmOutputSelection {
317 All,
318 Assembly,
319 LegacyAssembly,
320 MethodIdentifiers,
321 GasEstimates,
322 ByteCode(BytecodeOutputSelection),
323 DeployedByteCode(DeployedBytecodeOutputSelection),
324}
325
326impl From<BytecodeOutputSelection> for EvmOutputSelection {
327 fn from(b: BytecodeOutputSelection) -> Self {
328 Self::ByteCode(b)
329 }
330}
331
332impl From<DeployedBytecodeOutputSelection> for EvmOutputSelection {
333 fn from(b: DeployedBytecodeOutputSelection) -> Self {
334 Self::DeployedByteCode(b)
335 }
336}
337
338impl Serialize for EvmOutputSelection {
339 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
340 where
341 S: Serializer,
342 {
343 serializer.collect_str(self)
344 }
345}
346
347impl<'de> Deserialize<'de> for EvmOutputSelection {
348 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
349 where
350 D: Deserializer<'de>,
351 {
352 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
353 }
354}
355
356impl fmt::Display for EvmOutputSelection {
357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 match self {
359 Self::All => f.write_str("evm"),
360 Self::Assembly => f.write_str("evm.assembly"),
361 Self::LegacyAssembly => f.write_str("evm.legacyAssembly"),
362 Self::MethodIdentifiers => f.write_str("evm.methodIdentifiers"),
363 Self::GasEstimates => f.write_str("evm.gasEstimates"),
364 Self::ByteCode(b) => b.fmt(f),
365 Self::DeployedByteCode(b) => b.fmt(f),
366 }
367 }
368}
369
370impl FromStr for EvmOutputSelection {
371 type Err = String;
372
373 fn from_str(s: &str) -> Result<Self, Self::Err> {
374 match s {
375 "evm" => Ok(Self::All),
376 "asm" | "evm.assembly" => Ok(Self::Assembly),
377 "legacyAssembly" | "evm.legacyAssembly" => Ok(Self::LegacyAssembly),
378 "methodidentifiers" | "evm.methodIdentifiers" | "evm.methodidentifiers" => {
379 Ok(Self::MethodIdentifiers)
380 }
381 "gas" | "evm.gasEstimates" | "evm.gasestimates" => Ok(Self::GasEstimates),
382 s => BytecodeOutputSelection::from_str(s)
383 .map(EvmOutputSelection::ByteCode)
384 .or_else(|_| {
385 DeployedBytecodeOutputSelection::from_str(s)
386 .map(EvmOutputSelection::DeployedByteCode)
387 })
388 .map_err(|_| format!("Invalid evm selection: {s}")),
389 }
390 }
391}
392
393#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
395pub enum BytecodeOutputSelection {
396 All,
397 FunctionDebugData,
398 Object,
399 Opcodes,
400 SourceMap,
401 LinkReferences,
402 GeneratedSources,
403}
404
405impl Serialize for BytecodeOutputSelection {
406 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
407 where
408 S: Serializer,
409 {
410 serializer.collect_str(self)
411 }
412}
413
414impl<'de> Deserialize<'de> for BytecodeOutputSelection {
415 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
416 where
417 D: Deserializer<'de>,
418 {
419 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
420 }
421}
422
423impl fmt::Display for BytecodeOutputSelection {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 match self {
426 Self::All => f.write_str("evm.bytecode"),
427 Self::FunctionDebugData => f.write_str("evm.bytecode.functionDebugData"),
428 Self::Object => f.write_str("evm.bytecode.object"),
429 Self::Opcodes => f.write_str("evm.bytecode.opcodes"),
430 Self::SourceMap => f.write_str("evm.bytecode.sourceMap"),
431 Self::LinkReferences => f.write_str("evm.bytecode.linkReferences"),
432 Self::GeneratedSources => f.write_str("evm.bytecode.generatedSources"),
433 }
434 }
435}
436
437impl FromStr for BytecodeOutputSelection {
438 type Err = String;
439
440 fn from_str(s: &str) -> Result<Self, Self::Err> {
441 match s {
442 "evm.bytecode" => Ok(Self::All),
443 "evm.bytecode.functionDebugData" => Ok(Self::FunctionDebugData),
444 "code" | "bin" | "evm.bytecode.object" => Ok(Self::Object),
445 "evm.bytecode.opcodes" => Ok(Self::Opcodes),
446 "evm.bytecode.sourceMap" => Ok(Self::SourceMap),
447 "evm.bytecode.linkReferences" => Ok(Self::LinkReferences),
448 "evm.bytecode.generatedSources" => Ok(Self::GeneratedSources),
449 s => Err(format!("Invalid bytecode selection: {s}")),
450 }
451 }
452}
453
454#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
456pub enum DeployedBytecodeOutputSelection {
457 All,
458 FunctionDebugData,
459 Object,
460 Opcodes,
461 SourceMap,
462 LinkReferences,
463 GeneratedSources,
464 ImmutableReferences,
465}
466
467impl Serialize for DeployedBytecodeOutputSelection {
468 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
469 where
470 S: Serializer,
471 {
472 serializer.collect_str(self)
473 }
474}
475
476impl<'de> Deserialize<'de> for DeployedBytecodeOutputSelection {
477 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
478 where
479 D: Deserializer<'de>,
480 {
481 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
482 }
483}
484
485impl fmt::Display for DeployedBytecodeOutputSelection {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 match self {
488 Self::All => f.write_str("evm.deployedBytecode"),
489 Self::FunctionDebugData => f.write_str("evm.deployedBytecode.functionDebugData"),
490 Self::Object => f.write_str("evm.deployedBytecode.object"),
491 Self::Opcodes => f.write_str("evm.deployedBytecode.opcodes"),
492 Self::SourceMap => f.write_str("evm.deployedBytecode.sourceMap"),
493 Self::LinkReferences => f.write_str("evm.deployedBytecode.linkReferences"),
494 Self::GeneratedSources => f.write_str("evm.deployedBytecode.generatedSources"),
495 Self::ImmutableReferences => f.write_str("evm.deployedBytecode.immutableReferences"),
496 }
497 }
498}
499
500impl FromStr for DeployedBytecodeOutputSelection {
501 type Err = String;
502
503 fn from_str(s: &str) -> Result<Self, Self::Err> {
504 match s {
505 "evm.deployedBytecode" => Ok(Self::All),
506 "evm.deployedBytecode.functionDebugData" => Ok(Self::FunctionDebugData),
507 "deployed-code"
508 | "deployed-bin"
509 | "runtime-code"
510 | "runtime-bin"
511 | "evm.deployedBytecode.object" => Ok(Self::Object),
512 "evm.deployedBytecode.opcodes" => Ok(Self::Opcodes),
513 "evm.deployedBytecode.sourceMap" => Ok(Self::SourceMap),
514 "evm.deployedBytecode.linkReferences" => Ok(Self::LinkReferences),
515 "evm.deployedBytecode.generatedSources" => Ok(Self::GeneratedSources),
516 "evm.deployedBytecode.immutableReferences" => Ok(Self::ImmutableReferences),
517 s => Err(format!("Invalid deployedBytecode selection: {s}")),
518 }
519 }
520}
521
522#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
524pub enum EwasmOutputSelection {
525 All,
526 Wast,
527 Wasm,
528}
529
530impl Serialize for EwasmOutputSelection {
531 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
532 where
533 S: Serializer,
534 {
535 serializer.collect_str(self)
536 }
537}
538
539impl<'de> Deserialize<'de> for EwasmOutputSelection {
540 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
541 where
542 D: Deserializer<'de>,
543 {
544 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
545 }
546}
547
548impl fmt::Display for EwasmOutputSelection {
549 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550 match self {
551 Self::All => f.write_str("ewasm"),
552 Self::Wast => f.write_str("ewasm.wast"),
553 Self::Wasm => f.write_str("ewasm.wasm"),
554 }
555 }
556}
557
558impl FromStr for EwasmOutputSelection {
559 type Err = String;
560
561 fn from_str(s: &str) -> Result<Self, Self::Err> {
562 match s {
563 "ewasm" => Ok(Self::All),
564 "ewasm.wast" => Ok(Self::Wast),
565 "ewasm.wasm" => Ok(Self::Wasm),
566 s => Err(format!("Invalid ewasm selection: {s}")),
567 }
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn outputselection_serde_works() {
577 let mut output = BTreeMap::default();
578 output.insert(
579 "*".to_string(),
580 vec![
581 "abi".to_string(),
582 "evm.bytecode".to_string(),
583 "evm.deployedBytecode".to_string(),
584 "evm.methodIdentifiers".to_string(),
585 ],
586 );
587
588 let json = serde_json::to_string(&output).unwrap();
589 let deserde_selection: BTreeMap<String, Vec<ContractOutputSelection>> =
590 serde_json::from_str(&json).unwrap();
591
592 assert_eq!(json, serde_json::to_string(&deserde_selection).unwrap());
593 }
594
595 #[test]
596 fn empty_outputselection_serde_works() {
597 let mut empty = OutputSelection::default();
598 empty.0.insert("contract.sol".to_string(), OutputSelection::empty_file_output_select());
599 let s = serde_json::to_string(&empty).unwrap();
600 assert_eq!(s, r#"{"contract.sol":{"*":[]}}"#);
601 }
602
603 #[test]
604 fn outputselection_subset_of() {
605 let output_selection = OutputSelection::from(BTreeMap::from([(
606 "*".to_string(),
607 BTreeMap::from([(
608 "*".to_string(),
609 vec!["abi".to_string(), "evm.bytecode".to_string()],
610 )]),
611 )]));
612
613 let output_selection_abi = OutputSelection::from(BTreeMap::from([(
614 "*".to_string(),
615 BTreeMap::from([("*".to_string(), vec!["abi".to_string()])]),
616 )]));
617
618 assert!(output_selection_abi.is_subset_of(&output_selection));
619 assert!(!output_selection.is_subset_of(&output_selection_abi));
620
621 let output_selection_empty = OutputSelection::from(BTreeMap::from([(
622 "*".to_string(),
623 BTreeMap::from([("*".to_string(), vec![])]),
624 )]));
625
626 assert!(output_selection_empty.is_subset_of(&output_selection));
627 assert!(output_selection_empty.is_subset_of(&output_selection_abi));
628 assert!(!output_selection.is_subset_of(&output_selection_empty));
629 assert!(!output_selection_abi.is_subset_of(&output_selection_empty));
630
631 let output_selection_specific = OutputSelection::from(BTreeMap::from([(
632 "Contract.sol".to_string(),
633 BTreeMap::from([(
634 "Contract".to_string(),
635 vec![
636 "abi".to_string(),
637 "evm.bytecode".to_string(),
638 "evm.deployedBytecode".to_string(),
639 ],
640 )]),
641 )]));
642
643 assert!(!output_selection_specific.is_subset_of(&output_selection));
644 }
645
646 #[test]
647 fn deployed_bytecode_from_str() {
648 assert_eq!(
649 DeployedBytecodeOutputSelection::from_str("evm.deployedBytecode.immutableReferences")
650 .unwrap(),
651 DeployedBytecodeOutputSelection::ImmutableReferences
652 )
653 }
654
655 #[test]
656 fn test_contains_ast() {
657 let ast_selection = OutputSelection::ast_output_selection();
659 assert!(ast_selection.contains_ast());
660
661 let default_selection = OutputSelection::default_output_selection();
663 assert!(!default_selection.contains_ast());
664
665 let complete_selection = OutputSelection::complete_output_selection();
667 assert!(complete_selection.contains_ast());
668
669 let empty_selection = OutputSelection::from(BTreeMap::from([(
671 "*".to_string(),
672 BTreeMap::from([("*".to_string(), vec![])]),
673 )]));
674 assert!(!empty_selection.contains_ast());
675
676 let explicit_ast = OutputSelection::from(BTreeMap::from([(
678 "*".to_string(),
679 BTreeMap::from([
680 ("*".to_string(), vec!["abi".to_string()]),
681 (String::new(), vec!["ast".to_string()]),
682 ]),
683 )]));
684 assert!(explicit_ast.contains_ast());
685
686 let contract_only = OutputSelection::from(BTreeMap::from([(
688 "*".to_string(),
689 BTreeMap::from([(
690 "*".to_string(),
691 vec!["abi".to_string(), "evm.bytecode".to_string()],
692 )]),
693 )]));
694 assert!(!contract_only.contains_ast());
695 }
696}