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
132impl Serialize for OutputSelection {
136 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137 where
138 S: Serializer,
139 {
140 struct EmptyFileOutput;
141
142 impl Serialize for EmptyFileOutput {
143 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144 where
145 S: Serializer,
146 {
147 let mut map = serializer.serialize_map(Some(1))?;
148 map.serialize_entry("*", &[] as &[String])?;
149 map.end()
150 }
151 }
152
153 let mut map = serializer.serialize_map(Some(self.0.len()))?;
154 for (file, selection) in self.0.iter() {
155 if selection.is_empty() {
156 map.serialize_entry(file, &EmptyFileOutput {})?;
157 } else {
158 map.serialize_entry(file, selection)?;
159 }
160 }
161 map.end()
162 }
163}
164
165impl AsRef<BTreeMap<String, FileOutputSelection>> for OutputSelection {
166 fn as_ref(&self) -> &BTreeMap<String, FileOutputSelection> {
167 &self.0
168 }
169}
170
171impl AsMut<BTreeMap<String, FileOutputSelection>> for OutputSelection {
172 fn as_mut(&mut self) -> &mut BTreeMap<String, FileOutputSelection> {
173 &mut self.0
174 }
175}
176
177impl From<BTreeMap<String, FileOutputSelection>> for OutputSelection {
178 fn from(s: BTreeMap<String, FileOutputSelection>) -> Self {
179 Self(s)
180 }
181}
182
183#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
185pub enum ContractOutputSelection {
186 Abi,
187 DevDoc,
188 UserDoc,
189 Metadata,
190 Ir,
191 IrOptimized,
192 IrOptimizedAst,
193 StorageLayout,
194 TransientStorageLayout,
195 Evm(EvmOutputSelection),
196 Ewasm(EwasmOutputSelection),
197}
198
199impl ContractOutputSelection {
200 pub fn basic() -> Vec<Self> {
206 vec![
209 Self::Abi,
210 BytecodeOutputSelection::Object.into(),
212 BytecodeOutputSelection::SourceMap.into(),
213 BytecodeOutputSelection::LinkReferences.into(),
214 DeployedBytecodeOutputSelection::Object.into(),
216 DeployedBytecodeOutputSelection::SourceMap.into(),
217 DeployedBytecodeOutputSelection::LinkReferences.into(),
218 DeployedBytecodeOutputSelection::ImmutableReferences.into(),
219 EvmOutputSelection::MethodIdentifiers.into(),
220 ]
221 }
222}
223
224impl Serialize for ContractOutputSelection {
225 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
226 where
227 S: Serializer,
228 {
229 serializer.collect_str(self)
230 }
231}
232
233impl<'de> Deserialize<'de> for ContractOutputSelection {
234 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
235 where
236 D: Deserializer<'de>,
237 {
238 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
239 }
240}
241
242impl fmt::Display for ContractOutputSelection {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 Self::Abi => f.write_str("abi"),
246 Self::DevDoc => f.write_str("devdoc"),
247 Self::UserDoc => f.write_str("userdoc"),
248 Self::Metadata => f.write_str("metadata"),
249 Self::Ir => f.write_str("ir"),
250 Self::IrOptimized => f.write_str("irOptimized"),
251 Self::IrOptimizedAst => f.write_str("irOptimizedAst"),
252 Self::StorageLayout => f.write_str("storageLayout"),
253 Self::TransientStorageLayout => f.write_str("transientStorageLayout"),
254 Self::Evm(e) => e.fmt(f),
255 Self::Ewasm(e) => e.fmt(f),
256 }
257 }
258}
259
260impl FromStr for ContractOutputSelection {
261 type Err = String;
262
263 fn from_str(s: &str) -> Result<Self, Self::Err> {
264 match s {
265 "abi" => Ok(Self::Abi),
266 "devdoc" => Ok(Self::DevDoc),
267 "userdoc" => Ok(Self::UserDoc),
268 "metadata" => Ok(Self::Metadata),
269 "ir" => Ok(Self::Ir),
270 "ir-optimized" | "irOptimized" | "iroptimized" => Ok(Self::IrOptimized),
271 "irOptimizedAst" | "ir-optimized-ast" | "iroptimizedast" => Ok(Self::IrOptimizedAst),
272 "storage-layout" | "storagelayout" | "storageLayout" => Ok(Self::StorageLayout),
273 "transient-storage-layout" | "transientstoragelayout" | "transientStorageLayout" => {
274 Ok(Self::TransientStorageLayout)
275 }
276 s => EvmOutputSelection::from_str(s)
277 .map(ContractOutputSelection::Evm)
278 .or_else(|_| EwasmOutputSelection::from_str(s).map(ContractOutputSelection::Ewasm))
279 .map_err(|_| format!("Invalid contract output selection: {s}")),
280 }
281 }
282}
283
284impl<T: Into<EvmOutputSelection>> From<T> for ContractOutputSelection {
285 fn from(evm: T) -> Self {
286 Self::Evm(evm.into())
287 }
288}
289
290impl From<EwasmOutputSelection> for ContractOutputSelection {
291 fn from(ewasm: EwasmOutputSelection) -> Self {
292 Self::Ewasm(ewasm)
293 }
294}
295
296#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
298pub enum EvmOutputSelection {
299 All,
300 Assembly,
301 LegacyAssembly,
302 MethodIdentifiers,
303 GasEstimates,
304 ByteCode(BytecodeOutputSelection),
305 DeployedByteCode(DeployedBytecodeOutputSelection),
306}
307
308impl From<BytecodeOutputSelection> for EvmOutputSelection {
309 fn from(b: BytecodeOutputSelection) -> Self {
310 Self::ByteCode(b)
311 }
312}
313
314impl From<DeployedBytecodeOutputSelection> for EvmOutputSelection {
315 fn from(b: DeployedBytecodeOutputSelection) -> Self {
316 Self::DeployedByteCode(b)
317 }
318}
319
320impl Serialize for EvmOutputSelection {
321 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
322 where
323 S: Serializer,
324 {
325 serializer.collect_str(self)
326 }
327}
328
329impl<'de> Deserialize<'de> for EvmOutputSelection {
330 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
331 where
332 D: Deserializer<'de>,
333 {
334 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
335 }
336}
337
338impl fmt::Display for EvmOutputSelection {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 match self {
341 Self::All => f.write_str("evm"),
342 Self::Assembly => f.write_str("evm.assembly"),
343 Self::LegacyAssembly => f.write_str("evm.legacyAssembly"),
344 Self::MethodIdentifiers => f.write_str("evm.methodIdentifiers"),
345 Self::GasEstimates => f.write_str("evm.gasEstimates"),
346 Self::ByteCode(b) => b.fmt(f),
347 Self::DeployedByteCode(b) => b.fmt(f),
348 }
349 }
350}
351
352impl FromStr for EvmOutputSelection {
353 type Err = String;
354
355 fn from_str(s: &str) -> Result<Self, Self::Err> {
356 match s {
357 "evm" => Ok(Self::All),
358 "asm" | "evm.assembly" => Ok(Self::Assembly),
359 "legacyAssembly" | "evm.legacyAssembly" => Ok(Self::LegacyAssembly),
360 "methodidentifiers" | "evm.methodIdentifiers" | "evm.methodidentifiers" => {
361 Ok(Self::MethodIdentifiers)
362 }
363 "gas" | "evm.gasEstimates" | "evm.gasestimates" => Ok(Self::GasEstimates),
364 s => BytecodeOutputSelection::from_str(s)
365 .map(EvmOutputSelection::ByteCode)
366 .or_else(|_| {
367 DeployedBytecodeOutputSelection::from_str(s)
368 .map(EvmOutputSelection::DeployedByteCode)
369 })
370 .map_err(|_| format!("Invalid evm selection: {s}")),
371 }
372 }
373}
374
375#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
377pub enum BytecodeOutputSelection {
378 All,
379 FunctionDebugData,
380 Object,
381 Opcodes,
382 SourceMap,
383 LinkReferences,
384 GeneratedSources,
385}
386
387impl Serialize for BytecodeOutputSelection {
388 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
389 where
390 S: Serializer,
391 {
392 serializer.collect_str(self)
393 }
394}
395
396impl<'de> Deserialize<'de> for BytecodeOutputSelection {
397 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
398 where
399 D: Deserializer<'de>,
400 {
401 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
402 }
403}
404
405impl fmt::Display for BytecodeOutputSelection {
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 match self {
408 Self::All => f.write_str("evm.bytecode"),
409 Self::FunctionDebugData => f.write_str("evm.bytecode.functionDebugData"),
410 Self::Object => f.write_str("evm.bytecode.object"),
411 Self::Opcodes => f.write_str("evm.bytecode.opcodes"),
412 Self::SourceMap => f.write_str("evm.bytecode.sourceMap"),
413 Self::LinkReferences => f.write_str("evm.bytecode.linkReferences"),
414 Self::GeneratedSources => f.write_str("evm.bytecode.generatedSources"),
415 }
416 }
417}
418
419impl FromStr for BytecodeOutputSelection {
420 type Err = String;
421
422 fn from_str(s: &str) -> Result<Self, Self::Err> {
423 match s {
424 "evm.bytecode" => Ok(Self::All),
425 "evm.bytecode.functionDebugData" => Ok(Self::FunctionDebugData),
426 "code" | "bin" | "evm.bytecode.object" => Ok(Self::Object),
427 "evm.bytecode.opcodes" => Ok(Self::Opcodes),
428 "evm.bytecode.sourceMap" => Ok(Self::SourceMap),
429 "evm.bytecode.linkReferences" => Ok(Self::LinkReferences),
430 "evm.bytecode.generatedSources" => Ok(Self::GeneratedSources),
431 s => Err(format!("Invalid bytecode selection: {s}")),
432 }
433 }
434}
435
436#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
438pub enum DeployedBytecodeOutputSelection {
439 All,
440 FunctionDebugData,
441 Object,
442 Opcodes,
443 SourceMap,
444 LinkReferences,
445 GeneratedSources,
446 ImmutableReferences,
447}
448
449impl Serialize for DeployedBytecodeOutputSelection {
450 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
451 where
452 S: Serializer,
453 {
454 serializer.collect_str(self)
455 }
456}
457
458impl<'de> Deserialize<'de> for DeployedBytecodeOutputSelection {
459 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
460 where
461 D: Deserializer<'de>,
462 {
463 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
464 }
465}
466
467impl fmt::Display for DeployedBytecodeOutputSelection {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469 match self {
470 Self::All => f.write_str("evm.deployedBytecode"),
471 Self::FunctionDebugData => f.write_str("evm.deployedBytecode.functionDebugData"),
472 Self::Object => f.write_str("evm.deployedBytecode.object"),
473 Self::Opcodes => f.write_str("evm.deployedBytecode.opcodes"),
474 Self::SourceMap => f.write_str("evm.deployedBytecode.sourceMap"),
475 Self::LinkReferences => f.write_str("evm.deployedBytecode.linkReferences"),
476 Self::GeneratedSources => f.write_str("evm.deployedBytecode.generatedSources"),
477 Self::ImmutableReferences => f.write_str("evm.deployedBytecode.immutableReferences"),
478 }
479 }
480}
481
482impl FromStr for DeployedBytecodeOutputSelection {
483 type Err = String;
484
485 fn from_str(s: &str) -> Result<Self, Self::Err> {
486 match s {
487 "evm.deployedBytecode" => Ok(Self::All),
488 "evm.deployedBytecode.functionDebugData" => Ok(Self::FunctionDebugData),
489 "deployed-code"
490 | "deployed-bin"
491 | "runtime-code"
492 | "runtime-bin"
493 | "evm.deployedBytecode.object" => Ok(Self::Object),
494 "evm.deployedBytecode.opcodes" => Ok(Self::Opcodes),
495 "evm.deployedBytecode.sourceMap" => Ok(Self::SourceMap),
496 "evm.deployedBytecode.linkReferences" => Ok(Self::LinkReferences),
497 "evm.deployedBytecode.generatedSources" => Ok(Self::GeneratedSources),
498 "evm.deployedBytecode.immutableReferences" => Ok(Self::ImmutableReferences),
499 s => Err(format!("Invalid deployedBytecode selection: {s}")),
500 }
501 }
502}
503
504#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
506pub enum EwasmOutputSelection {
507 All,
508 Wast,
509 Wasm,
510}
511
512impl Serialize for EwasmOutputSelection {
513 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
514 where
515 S: Serializer,
516 {
517 serializer.collect_str(self)
518 }
519}
520
521impl<'de> Deserialize<'de> for EwasmOutputSelection {
522 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
523 where
524 D: Deserializer<'de>,
525 {
526 String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
527 }
528}
529
530impl fmt::Display for EwasmOutputSelection {
531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532 match self {
533 Self::All => f.write_str("ewasm"),
534 Self::Wast => f.write_str("ewasm.wast"),
535 Self::Wasm => f.write_str("ewasm.wasm"),
536 }
537 }
538}
539
540impl FromStr for EwasmOutputSelection {
541 type Err = String;
542
543 fn from_str(s: &str) -> Result<Self, Self::Err> {
544 match s {
545 "ewasm" => Ok(Self::All),
546 "ewasm.wast" => Ok(Self::Wast),
547 "ewasm.wasm" => Ok(Self::Wasm),
548 s => Err(format!("Invalid ewasm selection: {s}")),
549 }
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556
557 #[test]
558 fn outputselection_serde_works() {
559 let mut output = BTreeMap::default();
560 output.insert(
561 "*".to_string(),
562 vec![
563 "abi".to_string(),
564 "evm.bytecode".to_string(),
565 "evm.deployedBytecode".to_string(),
566 "evm.methodIdentifiers".to_string(),
567 ],
568 );
569
570 let json = serde_json::to_string(&output).unwrap();
571 let deserde_selection: BTreeMap<String, Vec<ContractOutputSelection>> =
572 serde_json::from_str(&json).unwrap();
573
574 assert_eq!(json, serde_json::to_string(&deserde_selection).unwrap());
575 }
576
577 #[test]
578 fn empty_outputselection_serde_works() {
579 let mut empty = OutputSelection::default();
580 empty.0.insert("contract.sol".to_string(), OutputSelection::empty_file_output_select());
581 let s = serde_json::to_string(&empty).unwrap();
582 assert_eq!(s, r#"{"contract.sol":{"*":[]}}"#);
583 }
584
585 #[test]
586 fn outputselection_subset_of() {
587 let output_selection = OutputSelection::from(BTreeMap::from([(
588 "*".to_string(),
589 BTreeMap::from([(
590 "*".to_string(),
591 vec!["abi".to_string(), "evm.bytecode".to_string()],
592 )]),
593 )]));
594
595 let output_selection_abi = OutputSelection::from(BTreeMap::from([(
596 "*".to_string(),
597 BTreeMap::from([("*".to_string(), vec!["abi".to_string()])]),
598 )]));
599
600 assert!(output_selection_abi.is_subset_of(&output_selection));
601 assert!(!output_selection.is_subset_of(&output_selection_abi));
602
603 let output_selection_empty = OutputSelection::from(BTreeMap::from([(
604 "*".to_string(),
605 BTreeMap::from([("*".to_string(), vec![])]),
606 )]));
607
608 assert!(output_selection_empty.is_subset_of(&output_selection));
609 assert!(output_selection_empty.is_subset_of(&output_selection_abi));
610 assert!(!output_selection.is_subset_of(&output_selection_empty));
611 assert!(!output_selection_abi.is_subset_of(&output_selection_empty));
612
613 let output_selecttion_specific = OutputSelection::from(BTreeMap::from([(
614 "Contract.sol".to_string(),
615 BTreeMap::from([(
616 "Contract".to_string(),
617 vec![
618 "abi".to_string(),
619 "evm.bytecode".to_string(),
620 "evm.deployedBytecode".to_string(),
621 ],
622 )]),
623 )]));
624
625 assert!(!output_selecttion_specific.is_subset_of(&output_selection));
626 }
627
628 #[test]
629 fn deployed_bytecode_from_str() {
630 assert_eq!(
631 DeployedBytecodeOutputSelection::from_str("evm.deployedBytecode.immutableReferences")
632 .unwrap(),
633 DeployedBytecodeOutputSelection::ImmutableReferences
634 )
635 }
636}