feat(solc): more options for output selection (#898)

* refactor: make artifacts a sub mod

* feat: more options for output selection

* chore: use tostring instead
This commit is contained in:
Matthias Seitz 2022-02-11 16:46:44 +01:00 committed by GitHub
parent 8f19ba1fa4
commit cdaac16f0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 519 additions and 127 deletions

View File

@ -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,18 +184,40 @@ pub struct Settings {
/// ```
#[serde(default)]
pub output_selection: BTreeMap<String, BTreeMap<String, Vec<String>>>,
#[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<EvmVersion>,
#[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
pub libraries: BTreeMap<String, BTreeMap<String, String>>,
}
impl Settings {
/// Creates a new `Settings` instance with the given `output_selection`
pub fn new(output_selection: BTreeMap<String, BTreeMap<String, Vec<String>>>) -> 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<String, BTreeMap<String, Vec<String>>> {
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<String, BTreeMap<String, Vec<String>>> {
let mut output_selection = BTreeMap::default();
let mut output = BTreeMap::default();
output.insert(
BTreeMap::from([(
"*".to_string(),
BTreeMap::from([(
"*".to_string(),
vec![
"abi".to_string(),
@ -199,13 +225,19 @@ impl Settings {
"evm.deployedBytecode".to_string(),
"evm.methodIdentifiers".to_string(),
],
);
output_selection.insert("*".to_string(), output);
output_selection
)]),
)])
}
/// Inserts the value for all files and contracts
pub fn push_output_selection(&mut self, value: impl Into<String>) {
///
/// ```
/// 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<String>,
value: impl Into<String>,
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<Item = impl Into<String>>) {
pub fn set_output_selection(&mut self, values: impl IntoIterator<Item = impl ToString>) {
self.set_contract_output_selection("*", values)
}
@ -240,12 +272,12 @@ impl Settings {
pub fn set_contract_output_selection(
&mut self,
key: impl Into<String>,
values: impl IntoIterator<Item = impl Into<String>>,
values: impl IntoIterator<Item = impl ToString>,
) {
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<Abi>,
#[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<Metadata>,
#[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<Storage>,
#[serde(default, deserialize_with = "default_for_null")]
#[serde(default, deserialize_with = "serde_helpers::default_for_null")]
pub types: BTreeMap<String, StorageType>,
}
@ -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<u64>,
pub message: String,
pub formatted_message: Option<String>,
@ -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<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
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<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
if let Some(s) = Option::<String>::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<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
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<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned,
{
if let Some(s) = Option::<String>::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<Bytes, D::Error>
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<Option<Bytes>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<String>::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<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -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<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 {
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<Self, Self::Err> {
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<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 {
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<Self, Self::Err> {
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<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 {
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<Self, Self::Err> {
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<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 {
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<Self, Self::Err> {
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<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 {
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<Self, Self::Err> {
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<String, Vec<ContractOutputSelection>> =
serde_json::from_str(&json).unwrap();
assert_eq!(json, serde_json::to_string(&deserde_selection).unwrap());
}
}

View File

@ -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<Bytes, D::Error>
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<Option<Bytes>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<String>::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<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
}
pub mod json_string_opt {
use serde::{
de::{self, DeserializeOwned},
ser, Deserialize, Deserializer, Serialize, Serializer,
};
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
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<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned,
{
if let Some(s) = Option::<String>::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<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
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<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
if let Some(s) = Option::<String>::deserialize(deserializer)? {
s.parse().map_err(de::Error::custom).map(Some)
} else {
Ok(None)
}
}
}