From cdaac16f0acc2d9dfd53b14f97e185351922c943 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 11 Feb 2022 16:46:44 +0100 Subject: [PATCH] feat(solc): more options for output selection (#898) * refactor: make artifacts a sub mod * feat: more options for output selection * chore: use tostring instead --- .../src/{artifacts.rs => artifacts/mod.rs} | 186 +++------ ethers-solc/src/artifacts/output_selection.rs | 352 ++++++++++++++++++ ethers-solc/src/artifacts/serde_helpers.rs | 108 ++++++ 3 files changed, 519 insertions(+), 127 deletions(-) rename ethers-solc/src/{artifacts.rs => artifacts/mod.rs} (94%) create mode 100644 ethers-solc/src/artifacts/output_selection.rs create mode 100644 ethers-solc/src/artifacts/serde_helpers.rs diff --git a/ethers-solc/src/artifacts.rs b/ethers-solc/src/artifacts/mod.rs similarity index 94% rename from ethers-solc/src/artifacts.rs rename to ethers-solc/src/artifacts/mod.rs index 60a58d9c..de43bc17 100644 --- a/ethers-solc/src/artifacts.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -22,6 +22,10 @@ use crate::{ use ethers_core::abi::Address; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +pub mod output_selection; +pub mod serde_helpers; +pub use serde_helpers::{deserialize_bytes, deserialize_opt_bytes}; + /// Solidity files are made up of multiple `source units`, a solidity contract is such a `source /// unit`, therefore a solidity file can contain multiple contracts: (1-N*) relationship. /// @@ -180,32 +184,60 @@ pub struct Settings { /// ``` #[serde(default)] pub output_selection: BTreeMap>>, - #[serde(default, with = "display_from_str_opt", skip_serializing_if = "Option::is_none")] + #[serde( + default, + with = "serde_helpers::display_from_str_opt", + skip_serializing_if = "Option::is_none" + )] pub evm_version: Option, #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] pub libraries: BTreeMap>, } impl Settings { + /// Creates a new `Settings` instance with the given `output_selection` + pub fn new(output_selection: BTreeMap>>) -> Self { + Self { output_selection, ..Default::default() } + } + + /// select all outputs the compiler can possibly generate, use + /// `{ "*": { "*": [ "*" ], "": [ "*" ] } }` + /// but note that this might slow down the compilation process needlessly. + pub fn complete_output_selection() -> BTreeMap>> { + BTreeMap::from([( + "*".to_string(), + BTreeMap::from([ + ("*".to_string(), vec!["*".to_string()]), + ("".to_string(), vec!["*".to_string()]), + ]), + )]) + } + /// Default output selection for compiler output pub fn default_output_selection() -> BTreeMap>> { - let mut output_selection = BTreeMap::default(); - let mut output = BTreeMap::default(); - output.insert( + BTreeMap::from([( "*".to_string(), - vec![ - "abi".to_string(), - "evm.bytecode".to_string(), - "evm.deployedBytecode".to_string(), - "evm.methodIdentifiers".to_string(), - ], - ); - output_selection.insert("*".to_string(), output); - output_selection + BTreeMap::from([( + "*".to_string(), + vec![ + "abi".to_string(), + "evm.bytecode".to_string(), + "evm.deployedBytecode".to_string(), + "evm.methodIdentifiers".to_string(), + ], + )]), + )]) } /// Inserts the value for all files and contracts - pub fn push_output_selection(&mut self, value: impl Into) { + /// + /// ``` + /// use ethers_solc::artifacts::output_selection::ContractOutputSelection; + /// use ethers_solc::artifacts::Settings; + /// let mut selection = Settings::default(); + /// selection.push_output_selection(ContractOutputSelection::Metadata); + /// ``` + pub fn push_output_selection(&mut self, value: impl ToString) { self.push_contract_output_selection("*", value) } @@ -215,9 +247,9 @@ impl Settings { pub fn push_contract_output_selection( &mut self, contracts: impl Into, - value: impl Into, + value: impl ToString, ) { - let value = value.into(); + let value = value.to_string(); let values = self .output_selection .entry("*".to_string()) @@ -230,7 +262,7 @@ impl Settings { } /// Sets the value for all files and contracts - pub fn set_output_selection(&mut self, values: impl IntoIterator>) { + pub fn set_output_selection(&mut self, values: impl IntoIterator) { self.set_contract_output_selection("*", values) } @@ -240,12 +272,12 @@ impl Settings { pub fn set_contract_output_selection( &mut self, key: impl Into, - values: impl IntoIterator>, + values: impl IntoIterator, ) { self.output_selection .entry("*".to_string()) .or_default() - .insert(key.into(), values.into_iter().map(Into::into).collect()); + .insert(key.into(), values.into_iter().map(|s| s.to_string()).collect()); } /// Adds `ast` to output @@ -792,7 +824,11 @@ pub struct Contract { /// The Ethereum Contract Metadata. /// See https://docs.soliditylang.org/en/develop/metadata.html pub abi: Option, - #[serde(default, skip_serializing_if = "Option::is_none", with = "json_string_opt")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "serde_helpers::json_string_opt" + )] pub metadata: Option, #[serde(default)] pub userdoc: UserDoc, @@ -1488,7 +1524,7 @@ impl Bytecode { #[serde(untagged)] pub enum BytecodeObject { /// Fully linked bytecode object - #[serde(deserialize_with = "deserialize_bytes")] + #[serde(deserialize_with = "serde_helpers::deserialize_bytes")] Bytecode(Bytes), /// Bytecode as hex string that's not fully linked yet and contains library placeholders Unlinked(String), @@ -1738,7 +1774,7 @@ pub struct Ewasm { #[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)] pub struct StorageLayout { pub storage: Vec, - #[serde(default, deserialize_with = "default_for_null")] + #[serde(default, deserialize_with = "serde_helpers::default_for_null")] pub types: BTreeMap, } @@ -1778,7 +1814,7 @@ pub struct Error { pub r#type: String, pub component: String, pub severity: Severity, - #[serde(default, with = "display_from_str_opt")] + #[serde(default, with = "serde_helpers::display_from_str_opt")] pub error_code: Option, pub message: String, pub formatted_message: Option, @@ -1935,110 +1971,6 @@ impl SourceFiles { } } -mod display_from_str_opt { - use serde::{de, Deserialize, Deserializer, Serializer}; - use std::{fmt, str::FromStr}; - - pub fn serialize(value: &Option, serializer: S) -> Result - where - T: fmt::Display, - S: Serializer, - { - if let Some(value) = value { - serializer.collect_str(value) - } else { - serializer.serialize_none() - } - } - - pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - T: FromStr, - T::Err: fmt::Display, - { - if let Some(s) = Option::::deserialize(deserializer)? { - s.parse().map_err(de::Error::custom).map(Some) - } else { - Ok(None) - } - } -} - -mod json_string_opt { - use serde::{ - de::{self, DeserializeOwned}, - ser, Deserialize, Deserializer, Serialize, Serializer, - }; - - pub fn serialize(value: &Option, serializer: S) -> Result - where - S: Serializer, - T: Serialize, - { - if let Some(value) = value { - let value = serde_json::to_string(value).map_err(ser::Error::custom)?; - serializer.serialize_str(&value) - } else { - serializer.serialize_none() - } - } - - pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - T: DeserializeOwned, - { - if let Some(s) = Option::::deserialize(deserializer)? { - serde_json::from_str(&s).map_err(de::Error::custom).map(Some) - } else { - Ok(None) - } - } -} - -pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result -where - D: Deserializer<'de>, -{ - let value = String::deserialize(d)?; - if let Some(value) = value.strip_prefix("0x") { - hex::decode(value) - } else { - hex::decode(&value) - } - .map(Into::into) - .map_err(|e| serde::de::Error::custom(e.to_string())) -} - -pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result, D::Error> -where - D: Deserializer<'de>, -{ - let value = Option::::deserialize(d)?; - if let Some(value) = value { - Ok(Some( - if let Some(value) = value.strip_prefix("0x") { - hex::decode(value) - } else { - hex::decode(&value) - } - .map_err(|e| serde::de::Error::custom(e.to_string()))? - .into(), - )) - } else { - Ok(None) - } -} - -fn default_for_null<'de, D, T>(deserializer: D) -> Result -where - D: Deserializer<'de>, - T: Deserialize<'de> + Default, -{ - Ok(Option::::deserialize(deserializer)?.unwrap_or_default()) -} - #[cfg(test)] mod tests { use super::*; diff --git a/ethers-solc/src/artifacts/output_selection.rs b/ethers-solc/src/artifacts/output_selection.rs new file mode 100644 index 00000000..4742fab9 --- /dev/null +++ b/ethers-solc/src/artifacts/output_selection.rs @@ -0,0 +1,352 @@ +//! bindings for standard json output selection + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{fmt, str::FromStr}; + +/// Contract level output selection +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ContractOutputSelection { + Abi, + DevDoc, + UserDoc, + Metadata, + Ir, + IrOptimized, + StorageLayout, + Evm(EvmOutputSelection), + Ewasm(EwasmOutputSelection), +} + +impl Serialize for ContractOutputSelection { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for ContractOutputSelection { + fn deserialize(deserializer: D) -> Result + 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 { + ContractOutputSelection::Abi => f.write_str("abi"), + ContractOutputSelection::DevDoc => f.write_str("devdoc"), + ContractOutputSelection::UserDoc => f.write_str("userdoc"), + ContractOutputSelection::Metadata => f.write_str("metadata"), + ContractOutputSelection::Ir => f.write_str("ir"), + ContractOutputSelection::IrOptimized => f.write_str("irOptimized"), + ContractOutputSelection::StorageLayout => f.write_str("storageLayout"), + ContractOutputSelection::Evm(e) => e.fmt(f), + ContractOutputSelection::Ewasm(e) => e.fmt(f), + } + } +} + +impl FromStr for ContractOutputSelection { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "abi" => Ok(ContractOutputSelection::Abi), + "devdoc" => Ok(ContractOutputSelection::DevDoc), + "userdoc" => Ok(ContractOutputSelection::UserDoc), + "metadata" => Ok(ContractOutputSelection::Metadata), + "ir" => Ok(ContractOutputSelection::Ir), + "irOptimized" => Ok(ContractOutputSelection::IrOptimized), + "storageLayout" => Ok(ContractOutputSelection::StorageLayout), + 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)), + } + } +} + +/// Contract level output selection for `evm` +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum EvmOutputSelection { + All, + Assembly, + LegacyAssembly, + MethodIdentifiers, + GasEstimates, + ByteCode(BytecodeOutputSelection), + DeployedByteCode(DeployedBytecodeOutputSelection), +} + +impl Serialize for EvmOutputSelection { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for EvmOutputSelection { + fn deserialize(deserializer: D) -> Result + 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 { + EvmOutputSelection::All => f.write_str("evm"), + EvmOutputSelection::Assembly => f.write_str("evm.assembly"), + EvmOutputSelection::LegacyAssembly => f.write_str("evm.legacyAssembly"), + EvmOutputSelection::MethodIdentifiers => f.write_str("evm.methodIdentifiers"), + EvmOutputSelection::GasEstimates => f.write_str("evm.gasEstimates"), + EvmOutputSelection::ByteCode(b) => b.fmt(f), + EvmOutputSelection::DeployedByteCode(b) => b.fmt(f), + } + } +} + +impl FromStr for EvmOutputSelection { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "evm" => Ok(EvmOutputSelection::All), + "evm.assembly" => Ok(EvmOutputSelection::Assembly), + "evm.legacyAssembly" => Ok(EvmOutputSelection::LegacyAssembly), + "evm.methodIdentifiers" => Ok(EvmOutputSelection::MethodIdentifiers), + "evm.gasEstimates" => Ok(EvmOutputSelection::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)), + } + } +} + +/// Contract level output selection for `evm.bytecode` +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BytecodeOutputSelection { + All, + FunctionDebugData, + Object, + Opcodes, + SourceMap, + LinkReferences, + GeneratedSources, +} + +impl Serialize for BytecodeOutputSelection { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for BytecodeOutputSelection { + fn deserialize(deserializer: D) -> Result + 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 { + BytecodeOutputSelection::All => f.write_str("evm.bytecode"), + BytecodeOutputSelection::FunctionDebugData => { + f.write_str("evm.bytecode.functionDebugData") + } + BytecodeOutputSelection::Object => f.write_str("evm.bytecode.object"), + BytecodeOutputSelection::Opcodes => f.write_str("evm.bytecode.opcodes"), + BytecodeOutputSelection::SourceMap => f.write_str("evm.bytecode.sourceMap"), + BytecodeOutputSelection::LinkReferences => f.write_str("evm.bytecode.linkReferences"), + BytecodeOutputSelection::GeneratedSources => { + f.write_str("evm.bytecode.generatedSources") + } + } + } +} + +impl FromStr for BytecodeOutputSelection { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "evm.bytecode" => Ok(BytecodeOutputSelection::All), + "evm.bytecode.functionDebugData" => Ok(BytecodeOutputSelection::FunctionDebugData), + "evm.bytecode.object" => Ok(BytecodeOutputSelection::Object), + "evm.bytecode.opcodes" => Ok(BytecodeOutputSelection::Opcodes), + "evm.bytecode.sourceMap" => Ok(BytecodeOutputSelection::SourceMap), + "evm.bytecode.linkReferences" => Ok(BytecodeOutputSelection::LinkReferences), + "evm.bytecode.generatedSources" => Ok(BytecodeOutputSelection::GeneratedSources), + s => Err(format!("Invalid bytecode selection: {}", s)), + } + } +} + +/// Contract level output selection for `evm.deployedBytecode` +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum DeployedBytecodeOutputSelection { + All, + FunctionDebugData, + Object, + Opcodes, + SourceMap, + LinkReferences, + GeneratedSources, +} + +impl Serialize for DeployedBytecodeOutputSelection { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for DeployedBytecodeOutputSelection { + fn deserialize(deserializer: D) -> Result + 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 { + DeployedBytecodeOutputSelection::All => f.write_str("evm.deployedBytecode"), + DeployedBytecodeOutputSelection::FunctionDebugData => { + f.write_str("evm.deployedBytecode.functionDebugData") + } + DeployedBytecodeOutputSelection::Object => f.write_str("evm.deployedBytecode.object"), + DeployedBytecodeOutputSelection::Opcodes => f.write_str("evm.deployedBytecode.opcodes"), + DeployedBytecodeOutputSelection::SourceMap => { + f.write_str("evm.deployedBytecode.sourceMap") + } + DeployedBytecodeOutputSelection::LinkReferences => { + f.write_str("evm.deployedBytecode.linkReferences") + } + DeployedBytecodeOutputSelection::GeneratedSources => { + f.write_str("evm.deployedBytecode.generatedSources") + } + } + } +} + +impl FromStr for DeployedBytecodeOutputSelection { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "evm.deployedBytecode" => Ok(DeployedBytecodeOutputSelection::All), + "evm.deployedBytecode.functionDebugData" => { + Ok(DeployedBytecodeOutputSelection::FunctionDebugData) + } + "evm.deployedBytecode.object" => Ok(DeployedBytecodeOutputSelection::Object), + "evm.deployedBytecode.opcodes" => Ok(DeployedBytecodeOutputSelection::Opcodes), + "evm.deployedBytecode.sourceMap" => Ok(DeployedBytecodeOutputSelection::SourceMap), + "evm.deployedBytecode.linkReferences" => { + Ok(DeployedBytecodeOutputSelection::LinkReferences) + } + "evm.deployedBytecode.generatedSources" => { + Ok(DeployedBytecodeOutputSelection::GeneratedSources) + } + s => Err(format!("Invalid deployedBytecode selection: {}", s)), + } + } +} + +/// Contract level output selection for `evm.ewasm` +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum EwasmOutputSelection { + All, + Wast, + Wasm, +} + +impl Serialize for EwasmOutputSelection { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for EwasmOutputSelection { + fn deserialize(deserializer: D) -> Result + 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 { + EwasmOutputSelection::All => f.write_str("ewasm"), + EwasmOutputSelection::Wast => f.write_str("ewasm.wast"), + EwasmOutputSelection::Wasm => f.write_str("ewasm.wasm"), + } + } +} + +impl FromStr for EwasmOutputSelection { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "ewasm" => Ok(EwasmOutputSelection::All), + "ewasm.wast" => Ok(EwasmOutputSelection::Wast), + "ewasm.wasm" => Ok(EwasmOutputSelection::Wasm), + s => Err(format!("Invalid ewasm selection: {}", s)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeMap; + + #[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> = + serde_json::from_str(&json).unwrap(); + + assert_eq!(json, serde_json::to_string(&deserde_selection).unwrap()); + } +} diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs new file mode 100644 index 00000000..6aba852e --- /dev/null +++ b/ethers-solc/src/artifacts/serde_helpers.rs @@ -0,0 +1,108 @@ +//! serde helpers + +use ethers_core::types::Bytes; +use serde::{Deserialize, Deserializer}; + +pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result +where + D: Deserializer<'de>, +{ + let value = String::deserialize(d)?; + if let Some(value) = value.strip_prefix("0x") { + hex::decode(value) + } else { + hex::decode(&value) + } + .map(Into::into) + .map_err(|e| serde::de::Error::custom(e.to_string())) +} + +pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let value = Option::::deserialize(d)?; + if let Some(value) = value { + Ok(Some( + if let Some(value) = value.strip_prefix("0x") { + hex::decode(value) + } else { + hex::decode(&value) + } + .map_err(|e| serde::de::Error::custom(e.to_string()))? + .into(), + )) + } else { + Ok(None) + } +} + +pub fn default_for_null<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: Deserialize<'de> + Default, +{ + Ok(Option::::deserialize(deserializer)?.unwrap_or_default()) +} + +pub mod json_string_opt { + use serde::{ + de::{self, DeserializeOwned}, + ser, Deserialize, Deserializer, Serialize, Serializer, + }; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + T: Serialize, + { + if let Some(value) = value { + let value = serde_json::to_string(value).map_err(ser::Error::custom)?; + serializer.serialize_str(&value) + } else { + serializer.serialize_none() + } + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: DeserializeOwned, + { + if let Some(s) = Option::::deserialize(deserializer)? { + serde_json::from_str(&s).map_err(de::Error::custom).map(Some) + } else { + Ok(None) + } + } +} + +pub mod display_from_str_opt { + use serde::{de, Deserialize, Deserializer, Serializer}; + use std::{fmt, str::FromStr}; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + T: fmt::Display, + S: Serializer, + { + if let Some(value) = value { + serializer.collect_str(value) + } else { + serializer.serialize_none() + } + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: FromStr, + T::Err: fmt::Display, + { + if let Some(s) = Option::::deserialize(deserializer)? { + s.parse().map_err(de::Error::custom).map(Some) + } else { + Ok(None) + } + } +}