use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
use std::{collections::BTreeMap, fmt, str::FromStr};
pub type FileOutputSelection = BTreeMap<String, Vec<String>>;
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(transparent)]
pub struct OutputSelection(pub BTreeMap<String, FileOutputSelection>);
impl OutputSelection {
pub fn complete_output_selection() -> Self {
BTreeMap::from([(
"*".to_string(),
BTreeMap::from([
("*".to_string(), vec!["*".to_string()]),
(String::new(), vec!["*".to_string()]),
]),
)])
.into()
}
pub fn default_output_selection() -> Self {
BTreeMap::from([("*".to_string(), Self::default_file_output_selection())]).into()
}
pub fn default_file_output_selection() -> FileOutputSelection {
BTreeMap::from([(
"*".to_string(),
vec![
"abi".to_string(),
"evm.bytecode".to_string(),
"evm.deployedBytecode".to_string(),
"evm.methodIdentifiers".to_string(),
],
)])
}
pub fn common_output_selection(outputs: impl IntoIterator<Item = String>) -> Self {
BTreeMap::from([(
"*".to_string(),
BTreeMap::from([("*".to_string(), outputs.into_iter().collect())]),
)])
.into()
}
pub fn empty_file_output_select() -> FileOutputSelection {
Default::default()
}
pub fn ast_output_selection() -> Self {
BTreeMap::from([(
"*".to_string(),
BTreeMap::from([
("*".to_string(), vec![]),
(String::new(), vec!["ast".to_string()]),
]),
)])
.into()
}
pub fn is_subset_of(&self, other: &Self) -> bool {
self.0.iter().all(|(file, selection)| {
other.0.get(file).map_or(false, |other_selection| {
selection.iter().all(|(contract, outputs)| {
other_selection.get(contract).map_or(false, |other_outputs| {
outputs.iter().all(|output| other_outputs.contains(output))
})
})
})
})
}
}
impl Serialize for OutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
struct EmptyFileOutput;
impl Serialize for EmptyFileOutput {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("*", &[] as &[String])?;
map.end()
}
}
let mut map = serializer.serialize_map(Some(self.0.len()))?;
for (file, selection) in self.0.iter() {
if selection.is_empty() {
map.serialize_entry(file, &EmptyFileOutput {})?;
} else {
map.serialize_entry(file, selection)?;
}
}
map.end()
}
}
impl AsRef<BTreeMap<String, FileOutputSelection>> for OutputSelection {
fn as_ref(&self) -> &BTreeMap<String, FileOutputSelection> {
&self.0
}
}
impl AsMut<BTreeMap<String, FileOutputSelection>> for OutputSelection {
fn as_mut(&mut self) -> &mut BTreeMap<String, FileOutputSelection> {
&mut self.0
}
}
impl From<BTreeMap<String, FileOutputSelection>> for OutputSelection {
fn from(s: BTreeMap<String, FileOutputSelection>) -> Self {
Self(s)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ContractOutputSelection {
Abi,
DevDoc,
UserDoc,
Metadata,
Ir,
IrOptimized,
IrOptimizedAst,
StorageLayout,
TransientStorageLayout,
Evm(EvmOutputSelection),
Ewasm(EwasmOutputSelection),
}
impl ContractOutputSelection {
pub fn basic() -> Vec<Self> {
vec![
Self::Abi,
BytecodeOutputSelection::All.into(),
DeployedBytecodeOutputSelection::All.into(),
EvmOutputSelection::MethodIdentifiers.into(),
]
}
}
impl Serialize for ContractOutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for ContractOutputSelection {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
}
}
impl fmt::Display for ContractOutputSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Abi => f.write_str("abi"),
Self::DevDoc => f.write_str("devdoc"),
Self::UserDoc => f.write_str("userdoc"),
Self::Metadata => f.write_str("metadata"),
Self::Ir => f.write_str("ir"),
Self::IrOptimized => f.write_str("irOptimized"),
Self::IrOptimizedAst => f.write_str("irOptimizedAst"),
Self::StorageLayout => f.write_str("storageLayout"),
Self::TransientStorageLayout => f.write_str("transientStorageLayout"),
Self::Evm(e) => e.fmt(f),
Self::Ewasm(e) => e.fmt(f),
}
}
}
impl FromStr for ContractOutputSelection {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"abi" => Ok(Self::Abi),
"devdoc" => Ok(Self::DevDoc),
"userdoc" => Ok(Self::UserDoc),
"metadata" => Ok(Self::Metadata),
"ir" => Ok(Self::Ir),
"ir-optimized" | "irOptimized" | "iroptimized" => Ok(Self::IrOptimized),
"irOptimizedAst" | "ir-optimized-ast" | "iroptimizedast" => Ok(Self::IrOptimizedAst),
"storage-layout" | "storagelayout" | "storageLayout" => Ok(Self::StorageLayout),
"transient-storage-layout" | "transientstoragelayout" | "transientStorageLayout" => {
Ok(Self::TransientStorageLayout)
}
s => EvmOutputSelection::from_str(s)
.map(ContractOutputSelection::Evm)
.or_else(|_| EwasmOutputSelection::from_str(s).map(ContractOutputSelection::Ewasm))
.map_err(|_| format!("Invalid contract output selection: {s}")),
}
}
}
impl<T: Into<EvmOutputSelection>> From<T> for ContractOutputSelection {
fn from(evm: T) -> Self {
Self::Evm(evm.into())
}
}
impl From<EwasmOutputSelection> for ContractOutputSelection {
fn from(ewasm: EwasmOutputSelection) -> Self {
Self::Ewasm(ewasm)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EvmOutputSelection {
All,
Assembly,
LegacyAssembly,
MethodIdentifiers,
GasEstimates,
ByteCode(BytecodeOutputSelection),
DeployedByteCode(DeployedBytecodeOutputSelection),
}
impl From<BytecodeOutputSelection> for EvmOutputSelection {
fn from(b: BytecodeOutputSelection) -> Self {
Self::ByteCode(b)
}
}
impl From<DeployedBytecodeOutputSelection> for EvmOutputSelection {
fn from(b: DeployedBytecodeOutputSelection) -> Self {
Self::DeployedByteCode(b)
}
}
impl Serialize for EvmOutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for EvmOutputSelection {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
}
}
impl fmt::Display for EvmOutputSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::All => f.write_str("evm"),
Self::Assembly => f.write_str("evm.assembly"),
Self::LegacyAssembly => f.write_str("evm.legacyAssembly"),
Self::MethodIdentifiers => f.write_str("evm.methodIdentifiers"),
Self::GasEstimates => f.write_str("evm.gasEstimates"),
Self::ByteCode(b) => b.fmt(f),
Self::DeployedByteCode(b) => b.fmt(f),
}
}
}
impl FromStr for EvmOutputSelection {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"evm" => Ok(Self::All),
"asm" | "evm.assembly" => Ok(Self::Assembly),
"legacyAssembly" | "evm.legacyAssembly" => Ok(Self::LegacyAssembly),
"methodidentifiers" | "evm.methodIdentifiers" | "evm.methodidentifiers" => {
Ok(Self::MethodIdentifiers)
}
"gas" | "evm.gasEstimates" | "evm.gasestimates" => Ok(Self::GasEstimates),
s => BytecodeOutputSelection::from_str(s)
.map(EvmOutputSelection::ByteCode)
.or_else(|_| {
DeployedBytecodeOutputSelection::from_str(s)
.map(EvmOutputSelection::DeployedByteCode)
})
.map_err(|_| format!("Invalid evm selection: {s}")),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BytecodeOutputSelection {
All,
FunctionDebugData,
Object,
Opcodes,
SourceMap,
LinkReferences,
GeneratedSources,
}
impl Serialize for BytecodeOutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for BytecodeOutputSelection {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
}
}
impl fmt::Display for BytecodeOutputSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::All => f.write_str("evm.bytecode"),
Self::FunctionDebugData => f.write_str("evm.bytecode.functionDebugData"),
Self::Object => f.write_str("evm.bytecode.object"),
Self::Opcodes => f.write_str("evm.bytecode.opcodes"),
Self::SourceMap => f.write_str("evm.bytecode.sourceMap"),
Self::LinkReferences => f.write_str("evm.bytecode.linkReferences"),
Self::GeneratedSources => f.write_str("evm.bytecode.generatedSources"),
}
}
}
impl FromStr for BytecodeOutputSelection {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"evm.bytecode" => Ok(Self::All),
"evm.bytecode.functionDebugData" => Ok(Self::FunctionDebugData),
"code" | "bin" | "evm.bytecode.object" => Ok(Self::Object),
"evm.bytecode.opcodes" => Ok(Self::Opcodes),
"evm.bytecode.sourceMap" => Ok(Self::SourceMap),
"evm.bytecode.linkReferences" => Ok(Self::LinkReferences),
"evm.bytecode.generatedSources" => Ok(Self::GeneratedSources),
s => Err(format!("Invalid bytecode selection: {s}")),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DeployedBytecodeOutputSelection {
All,
FunctionDebugData,
Object,
Opcodes,
SourceMap,
LinkReferences,
GeneratedSources,
ImmutableReferences,
}
impl Serialize for DeployedBytecodeOutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for DeployedBytecodeOutputSelection {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
}
}
impl fmt::Display for DeployedBytecodeOutputSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::All => f.write_str("evm.deployedBytecode"),
Self::FunctionDebugData => f.write_str("evm.deployedBytecode.functionDebugData"),
Self::Object => f.write_str("evm.deployedBytecode.object"),
Self::Opcodes => f.write_str("evm.deployedBytecode.opcodes"),
Self::SourceMap => f.write_str("evm.deployedBytecode.sourceMap"),
Self::LinkReferences => f.write_str("evm.deployedBytecode.linkReferences"),
Self::GeneratedSources => f.write_str("evm.deployedBytecode.generatedSources"),
Self::ImmutableReferences => f.write_str("evm.deployedBytecode.immutableReferences"),
}
}
}
impl FromStr for DeployedBytecodeOutputSelection {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"evm.deployedBytecode" => Ok(Self::All),
"evm.deployedBytecode.functionDebugData" => Ok(Self::FunctionDebugData),
"deployed-code"
| "deployed-bin"
| "runtime-code"
| "runtime-bin"
| "evm.deployedBytecode.object" => Ok(Self::Object),
"evm.deployedBytecode.opcodes" => Ok(Self::Opcodes),
"evm.deployedBytecode.sourceMap" => Ok(Self::SourceMap),
"evm.deployedBytecode.linkReferences" => Ok(Self::LinkReferences),
"evm.deployedBytecode.generatedSources" => Ok(Self::GeneratedSources),
"evm.deployedBytecode.immutableReferences" => Ok(Self::ImmutableReferences),
s => Err(format!("Invalid deployedBytecode selection: {s}")),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EwasmOutputSelection {
All,
Wast,
Wasm,
}
impl Serialize for EwasmOutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for EwasmOutputSelection {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom)
}
}
impl fmt::Display for EwasmOutputSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::All => f.write_str("ewasm"),
Self::Wast => f.write_str("ewasm.wast"),
Self::Wasm => f.write_str("ewasm.wasm"),
}
}
}
impl FromStr for EwasmOutputSelection {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ewasm" => Ok(Self::All),
"ewasm.wast" => Ok(Self::Wast),
"ewasm.wasm" => Ok(Self::Wasm),
s => Err(format!("Invalid ewasm selection: {s}")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn outputselection_serde_works() {
let mut output = BTreeMap::default();
output.insert(
"*".to_string(),
vec![
"abi".to_string(),
"evm.bytecode".to_string(),
"evm.deployedBytecode".to_string(),
"evm.methodIdentifiers".to_string(),
],
);
let json = serde_json::to_string(&output).unwrap();
let deserde_selection: BTreeMap<String, Vec<ContractOutputSelection>> =
serde_json::from_str(&json).unwrap();
assert_eq!(json, serde_json::to_string(&deserde_selection).unwrap());
}
#[test]
fn empty_outputselection_serde_works() {
let mut empty = OutputSelection::default();
empty.0.insert("contract.sol".to_string(), OutputSelection::empty_file_output_select());
let s = serde_json::to_string(&empty).unwrap();
assert_eq!(s, r#"{"contract.sol":{"*":[]}}"#);
}
#[test]
fn outputselection_subset_of() {
let output_selection = OutputSelection::from(BTreeMap::from([(
"*".to_string(),
BTreeMap::from([(
"*".to_string(),
vec!["abi".to_string(), "evm.bytecode".to_string()],
)]),
)]));
let output_selection_abi = OutputSelection::from(BTreeMap::from([(
"*".to_string(),
BTreeMap::from([("*".to_string(), vec!["abi".to_string()])]),
)]));
assert!(output_selection_abi.is_subset_of(&output_selection));
assert!(!output_selection.is_subset_of(&output_selection_abi));
let output_selection_empty = OutputSelection::from(BTreeMap::from([(
"*".to_string(),
BTreeMap::from([("*".to_string(), vec![])]),
)]));
assert!(output_selection_empty.is_subset_of(&output_selection));
assert!(output_selection_empty.is_subset_of(&output_selection_abi));
assert!(!output_selection.is_subset_of(&output_selection_empty));
assert!(!output_selection_abi.is_subset_of(&output_selection_empty));
let output_selecttion_specific = OutputSelection::from(BTreeMap::from([(
"Contract.sol".to_string(),
BTreeMap::from([(
"Contract".to_string(),
vec![
"abi".to_string(),
"evm.bytecode".to_string(),
"evm.deployedBytecode".to_string(),
],
)]),
)]));
assert!(!output_selecttion_specific.is_subset_of(&output_selection));
}
#[test]
fn deployed_bytecode_from_str() {
assert_eq!(
DeployedBytecodeOutputSelection::from_str("evm.deployedBytecode.immutableReferences")
.unwrap(),
DeployedBytecodeOutputSelection::ImmutableReferences
)
}
}