mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-23 18:58:00 +00:00
factor out solc JSON interface crate (#264)
The differential testing framework will make a second consumer. There seems to be no re-usable Rust crate for this. But we already have everything here, just needs a small refactor to make it fully re-usable. - Mostly decouple the solc JSON-input-output interface types from the `solidity` frontend crate - Expose the JSON-input-output interface types in a dedicated crate --------- Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
//! The `solc --standard-json` input language.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The `solc --standard-json` input language.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Language {
|
||||
/// The Solidity language.
|
||||
Solidity,
|
||||
/// The Yul IR.
|
||||
Yul,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Language {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Solidity => write!(f, "Solidity"),
|
||||
Self::Yul => write!(f, "Yul"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
//! The `solc --standard-json` input.
|
||||
|
||||
pub mod language;
|
||||
pub mod settings;
|
||||
pub mod source;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(all(feature = "parallel", feature = "resolc"))]
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
|
||||
use crate::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
|
||||
use crate::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::warning::Warning;
|
||||
|
||||
use self::language::Language;
|
||||
use self::settings::Settings;
|
||||
use self::source::Source;
|
||||
|
||||
/// The `solc --standard-json` input.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Input {
|
||||
/// The input language.
|
||||
pub language: Language,
|
||||
/// The input source code files hashmap.
|
||||
pub sources: BTreeMap<String, Source>,
|
||||
/// The compiler settings.
|
||||
pub settings: Settings,
|
||||
/// The suppressed warnings.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(skip_serializing)]
|
||||
pub suppressed_warnings: Option<Vec<Warning>>,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
/// A shortcut constructor from stdin.
|
||||
pub fn try_from_stdin() -> anyhow::Result<Self> {
|
||||
let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?;
|
||||
input
|
||||
.settings
|
||||
.output_selection
|
||||
.get_or_insert_with(SolcStandardJsonInputSettingsSelection::default)
|
||||
.extend_with_required();
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
/// A shortcut constructor from paths.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn try_from_paths(
|
||||
language: Language,
|
||||
evm_version: Option<revive_common::EVMVersion>,
|
||||
paths: &[PathBuf],
|
||||
library_map: Vec<String>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
output_selection: SolcStandardJsonInputSettingsSelection,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
|
||||
#[cfg(feature = "resolc")] suppressed_warnings: Option<Vec<Warning>>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut paths: BTreeSet<PathBuf> = paths.iter().cloned().collect();
|
||||
let libraries = Settings::parse_libraries(library_map)?;
|
||||
for library_file in libraries.keys() {
|
||||
paths.insert(PathBuf::from(library_file));
|
||||
}
|
||||
|
||||
let sources = paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let source = Source::try_from(path.as_path()).unwrap_or_else(|error| {
|
||||
panic!("Source code file {path:?} reading error: {error}")
|
||||
});
|
||||
(path.to_string_lossy().to_string(), source)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
language,
|
||||
sources,
|
||||
settings: Settings::new(
|
||||
evm_version,
|
||||
libraries,
|
||||
remappings,
|
||||
output_selection,
|
||||
optimizer,
|
||||
metadata,
|
||||
),
|
||||
#[cfg(feature = "resolc")]
|
||||
suppressed_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
/// A shortcut constructor from source code.
|
||||
/// Only for the integration test purposes.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn try_from_sources(
|
||||
evm_version: Option<revive_common::EVMVersion>,
|
||||
sources: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
output_selection: SolcStandardJsonInputSettingsSelection,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
|
||||
suppressed_warnings: Option<Vec<Warning>>,
|
||||
) -> anyhow::Result<Self> {
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = sources.into_par_iter(); // Parallel iterator
|
||||
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let iter = sources.into_iter(); // Sequential iterator
|
||||
let sources = iter
|
||||
.map(|(path, content)| (path, Source::from(content)))
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
language: Language::Solidity,
|
||||
sources,
|
||||
settings: Settings::new(
|
||||
evm_version,
|
||||
libraries,
|
||||
remappings,
|
||||
output_selection,
|
||||
optimizer,
|
||||
metadata,
|
||||
),
|
||||
suppressed_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the necessary defaults.
|
||||
pub fn normalize(&mut self, version: &semver::Version) {
|
||||
self.settings.normalize(version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//! The `solc --standard-json` input settings metadata.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::standard_json::input::settings::metadata_hash::MetadataHash;
|
||||
|
||||
/// The `solc --standard-json` input settings metadata.
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Metadata {
|
||||
/// The bytecode hash mode.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bytecode_hash: Option<MetadataHash>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(bytecode_hash: MetadataHash) -> Self {
|
||||
Self {
|
||||
bytecode_hash: Some(bytecode_hash),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//! The metadata hash mode.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The metadata hash mode.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum MetadataHash {
|
||||
/// Do not include bytecode hash.
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
/// The default keccak256 hash.
|
||||
#[serde(rename = "keccak256")]
|
||||
Keccak256,
|
||||
}
|
||||
|
||||
impl FromStr for MetadataHash {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"none" => Ok(Self::None),
|
||||
"keccak256" => Ok(Self::Keccak256),
|
||||
_ => anyhow::bail!("Unknown bytecode hash mode: `{}`", string),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//! The `solc --standard-json` input settings.
|
||||
|
||||
pub mod metadata;
|
||||
pub mod metadata_hash;
|
||||
pub mod optimizer;
|
||||
pub mod selection;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::metadata::Metadata;
|
||||
use self::optimizer::Optimizer;
|
||||
use self::selection::Selection;
|
||||
|
||||
/// The `solc --standard-json` input settings.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Settings {
|
||||
/// The target EVM version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub evm_version: Option<revive_common::EVMVersion>,
|
||||
/// The linker library addresses.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub libraries: Option<BTreeMap<String, BTreeMap<String, String>>>,
|
||||
/// The sorted list of remappings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remappings: Option<BTreeSet<String>>,
|
||||
/// The output selection filters.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_selection: Option<Selection>,
|
||||
/// Whether to compile via IR. Only for testing with solc >=0.8.13.
|
||||
#[serde(
|
||||
rename = "viaIR",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
skip_deserializing
|
||||
)]
|
||||
pub via_ir: Option<bool>,
|
||||
/// The optimizer settings.
|
||||
pub optimizer: Optimizer,
|
||||
/// The metadata settings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<Metadata>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
evm_version: Option<revive_common::EVMVersion>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
output_selection: Selection,
|
||||
optimizer: Optimizer,
|
||||
metadata: Option<Metadata>,
|
||||
) -> Self {
|
||||
Self {
|
||||
evm_version,
|
||||
libraries: Some(libraries),
|
||||
remappings,
|
||||
output_selection: Some(output_selection),
|
||||
optimizer,
|
||||
metadata,
|
||||
via_ir: Some(true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the necessary defaults.
|
||||
pub fn normalize(&mut self, version: &semver::Version) {
|
||||
self.optimizer.normalize(version);
|
||||
}
|
||||
|
||||
/// Parses the library list and returns their double hashmap with path and name as keys.
|
||||
pub fn parse_libraries(
|
||||
input: Vec<String>,
|
||||
) -> anyhow::Result<BTreeMap<String, BTreeMap<String, String>>> {
|
||||
let mut libraries = BTreeMap::new();
|
||||
for (index, library) in input.into_iter().enumerate() {
|
||||
let mut path_and_address = library.split('=');
|
||||
let path = path_and_address
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("The library #{} path is missing", index))?;
|
||||
let mut file_and_contract = path.split(':');
|
||||
let file = file_and_contract
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("The library `{}` file name is missing", path))?;
|
||||
let contract = file_and_contract.next().ok_or_else(|| {
|
||||
anyhow::anyhow!("The library `{}` contract name is missing", path)
|
||||
})?;
|
||||
let address = path_and_address
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("The library `{}` address is missing", path))?;
|
||||
libraries
|
||||
.entry(file.to_owned())
|
||||
.or_insert_with(BTreeMap::new)
|
||||
.insert(contract.to_owned(), address.to_owned());
|
||||
}
|
||||
Ok(libraries)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//! The `solc --standard-json` input settings optimizer details.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The `solc --standard-json` input settings optimizer details.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Details {
|
||||
/// Whether the pass is enabled.
|
||||
pub peephole: bool,
|
||||
/// Whether the pass is enabled.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub inliner: Option<bool>,
|
||||
/// Whether the pass is enabled.
|
||||
pub jumpdest_remover: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub order_literals: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub deduplicate: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub cse: bool,
|
||||
/// Whether the pass is enabled.
|
||||
pub constant_optimizer: bool,
|
||||
}
|
||||
|
||||
impl Details {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
peephole: bool,
|
||||
inliner: Option<bool>,
|
||||
jumpdest_remover: bool,
|
||||
order_literals: bool,
|
||||
deduplicate: bool,
|
||||
cse: bool,
|
||||
constant_optimizer: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
peephole,
|
||||
inliner,
|
||||
jumpdest_remover,
|
||||
order_literals,
|
||||
deduplicate,
|
||||
cse,
|
||||
constant_optimizer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a set of disabled optimizations.
|
||||
pub fn disabled(version: &semver::Version) -> Self {
|
||||
let inliner = if version >= &semver::Version::new(0, 8, 5) {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self::new(false, inliner, false, false, false, false, false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//! The `solc --standard-json` input settings optimizer.
|
||||
|
||||
pub mod details;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::details::Details;
|
||||
|
||||
/// The `solc --standard-json` input settings optimizer.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Optimizer {
|
||||
/// Whether the optimizer is enabled.
|
||||
pub enabled: bool,
|
||||
/// The optimization mode string.
|
||||
#[serde(skip_serializing)]
|
||||
pub mode: Option<char>,
|
||||
/// The `solc` optimizer details.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub details: Option<Details>,
|
||||
/// Whether to try to recompile with -Oz if the bytecode is too large.
|
||||
#[serde(skip_serializing)]
|
||||
pub fallback_to_optimizing_for_size: Option<bool>,
|
||||
}
|
||||
|
||||
impl Optimizer {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
enabled: bool,
|
||||
mode: Option<char>,
|
||||
version: &semver::Version,
|
||||
fallback_to_optimizing_for_size: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
mode,
|
||||
details: Some(Details::disabled(version)),
|
||||
fallback_to_optimizing_for_size: Some(fallback_to_optimizing_for_size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the necessary defaults.
|
||||
pub fn normalize(&mut self, version: &semver::Version) {
|
||||
self.details = if version >= &semver::Version::new(0, 5, 5) {
|
||||
Some(Details::disabled(version))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//! The `solc --standard-json` expected output selection flag.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The `solc --standard-json` expected output selection flag.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Flag {
|
||||
/// The ABI JSON.
|
||||
#[serde(rename = "abi")]
|
||||
ABI,
|
||||
/// The metadata.
|
||||
#[serde(rename = "metadata")]
|
||||
Metadata,
|
||||
/// The developer documentation.
|
||||
#[serde(rename = "devdoc")]
|
||||
Devdoc,
|
||||
/// The user documentation.
|
||||
#[serde(rename = "userdoc")]
|
||||
Userdoc,
|
||||
/// The function signature hashes JSON.
|
||||
#[serde(rename = "evm.methodIdentifiers")]
|
||||
MethodIdentifiers,
|
||||
/// The storage layout.
|
||||
#[serde(rename = "storageLayout")]
|
||||
StorageLayout,
|
||||
/// The AST JSON.
|
||||
#[serde(rename = "ast")]
|
||||
AST,
|
||||
/// The Yul IR.
|
||||
#[serde(rename = "irOptimized")]
|
||||
Yul,
|
||||
/// The EVM legacy assembly JSON.
|
||||
#[serde(rename = "evm.legacyAssembly")]
|
||||
EVMLA,
|
||||
#[serde(rename = "evm.bytecode")]
|
||||
EVMBC,
|
||||
#[serde(rename = "evm.deployedBytecode")]
|
||||
EVMDBC,
|
||||
/// The assembly code
|
||||
#[serde(rename = "evm.assembly")]
|
||||
Assembly,
|
||||
/// The Ir
|
||||
#[serde(rename = "ir")]
|
||||
Ir,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Flag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ABI => write!(f, "abi"),
|
||||
Self::Metadata => write!(f, "metadata"),
|
||||
Self::Devdoc => write!(f, "devdoc"),
|
||||
Self::Userdoc => write!(f, "userdoc"),
|
||||
Self::MethodIdentifiers => write!(f, "evm.methodIdentifiers"),
|
||||
Self::StorageLayout => write!(f, "storageLayout"),
|
||||
Self::AST => write!(f, "ast"),
|
||||
Self::Yul => write!(f, "irOptimized"),
|
||||
Self::EVMLA => write!(f, "evm.legacyAssembly"),
|
||||
Self::EVMBC => write!(f, "evm.bytecode"),
|
||||
Self::EVMDBC => write!(f, "evm.deployedBytecode"),
|
||||
Self::Assembly => write!(f, "evm.assembly"),
|
||||
Self::Ir => write!(f, "ir"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//! The `solc --standard-json` output file selection.
|
||||
|
||||
pub mod flag;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::flag::Flag as SelectionFlag;
|
||||
|
||||
/// The `solc --standard-json` output file selection.
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct File {
|
||||
/// The per-file output selections.
|
||||
#[serde(rename = "", skip_serializing_if = "Option::is_none")]
|
||||
pub per_file: Option<HashSet<SelectionFlag>>,
|
||||
/// The per-contract output selections.
|
||||
#[serde(rename = "*", skip_serializing_if = "Option::is_none")]
|
||||
pub per_contract: Option<HashSet<SelectionFlag>>,
|
||||
}
|
||||
|
||||
impl File {
|
||||
/// Creates the selection required by our compilation process.
|
||||
pub fn new_required() -> Self {
|
||||
Self {
|
||||
per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
|
||||
per_contract: Some(HashSet::from_iter([
|
||||
SelectionFlag::EVMBC,
|
||||
SelectionFlag::EVMDBC,
|
||||
SelectionFlag::MethodIdentifiers,
|
||||
SelectionFlag::Metadata,
|
||||
SelectionFlag::Yul,
|
||||
])),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the user's output selection with flag required by our compilation process.
|
||||
pub fn extend_with_required(&mut self) -> &mut Self {
|
||||
let required = Self::new_required();
|
||||
|
||||
self.per_file
|
||||
.get_or_insert_with(HashSet::default)
|
||||
.extend(required.per_file.unwrap_or_default());
|
||||
self.per_contract
|
||||
.get_or_insert_with(HashSet::default)
|
||||
.extend(required.per_contract.unwrap_or_default());
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//! The `solc --standard-json` output selection.
|
||||
|
||||
pub mod file;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::file::File as FileSelection;
|
||||
|
||||
/// The `solc --standard-json` output selection.
|
||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
|
||||
pub struct Selection {
|
||||
/// Only the 'all' wildcard is available for robustness reasons.
|
||||
#[serde(rename = "*", skip_serializing_if = "Option::is_none")]
|
||||
all: Option<FileSelection>,
|
||||
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty", flatten)]
|
||||
pub files: BTreeMap<String, FileSelection>,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
/// Creates the selection required by our compilation process.
|
||||
pub fn new_required() -> Self {
|
||||
Self {
|
||||
all: Some(FileSelection::new_required()),
|
||||
files: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the user's output selection with flag required by our compilation process.
|
||||
pub fn extend_with_required(&mut self) -> &mut Self {
|
||||
self.all
|
||||
.get_or_insert_with(FileSelection::new_required)
|
||||
.extend_with_required();
|
||||
for (_, v) in self.files.iter_mut() {
|
||||
v.extend_with_required();
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::SolcStandardJsonInputSettingsSelectionFile;
|
||||
|
||||
use super::Selection;
|
||||
|
||||
#[test]
|
||||
fn per_file() {
|
||||
let init = Selection {
|
||||
all: None,
|
||||
files: BTreeMap::from([(
|
||||
"Test".to_owned(),
|
||||
SolcStandardJsonInputSettingsSelectionFile::new_required(),
|
||||
)]),
|
||||
};
|
||||
|
||||
let deser = serde_json::to_string(&init)
|
||||
.and_then(|string| serde_json::from_str(&string))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(init, deser)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all() {
|
||||
let init = Selection {
|
||||
all: Some(SolcStandardJsonInputSettingsSelectionFile::new_required()),
|
||||
files: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let deser = serde_json::to_string(&init)
|
||||
.and_then(|string| serde_json::from_str(&string))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(init, deser)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_and_override() {
|
||||
let init = Selection {
|
||||
all: Some(SolcStandardJsonInputSettingsSelectionFile::new_required()),
|
||||
files: BTreeMap::from([(
|
||||
"Test".to_owned(),
|
||||
SolcStandardJsonInputSettingsSelectionFile::new_required(),
|
||||
)]),
|
||||
};
|
||||
|
||||
let deser = serde_json::to_string(&init)
|
||||
.and_then(|string| serde_json::from_str(&string))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(init, deser)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//! The `solc --standard-json` input source.
|
||||
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The `solc --standard-json` input source.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
/// The source code file content.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl From<String> for Source {
|
||||
fn from(content: String) -> Self {
|
||||
Self { content }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for Source {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(path: &Path) -> Result<Self, Self::Error> {
|
||||
let content = if path.to_string_lossy() == "-" {
|
||||
let mut solidity_code = String::with_capacity(16384);
|
||||
std::io::stdin()
|
||||
.read_to_string(&mut solidity_code)
|
||||
.map_err(|error| anyhow::anyhow!("<stdin> reading error: {}", error))?;
|
||||
solidity_code
|
||||
} else {
|
||||
std::fs::read_to_string(path)
|
||||
.map_err(|error| anyhow::anyhow!("File {:?} reading error: {}", path, error))?
|
||||
};
|
||||
|
||||
Ok(Self { content })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//! The `solc <input>.sol --standard-json` interface input and output.
|
||||
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
@@ -0,0 +1,34 @@
|
||||
//! The `solc --standard-json` output contract EVM bytecode.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The `solc --standard-json` output contract EVM bytecode.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Bytecode {
|
||||
/// The bytecode object.
|
||||
pub object: String,
|
||||
}
|
||||
|
||||
impl Bytecode {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(object: String) -> Self {
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
|
||||
/// The `solc --standard-json` output contract EVM deployed bytecode.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeployedBytecode {
|
||||
/// The bytecode object.
|
||||
pub object: String,
|
||||
}
|
||||
|
||||
impl DeployedBytecode {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(object: String) -> Self {
|
||||
Self { object }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//! The `solc --standard-json` output contract EVM data.
|
||||
|
||||
pub mod bytecode;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::bytecode::Bytecode;
|
||||
use self::bytecode::DeployedBytecode;
|
||||
|
||||
/// The `solc --standard-json` output contract EVM data.
|
||||
/// It is replaced by PolkaVM data after compiling.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EVM {
|
||||
/// The contract PolkaVM assembly code.
|
||||
#[serde(rename = "assembly", skip_serializing_if = "Option::is_none")]
|
||||
pub assembly_text: Option<String>,
|
||||
/// The contract bytecode.
|
||||
/// Is reset by that of PolkaVM before yielding the compiled project artifacts.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bytecode: Option<Bytecode>,
|
||||
/// The deployed bytecode of the contract.
|
||||
/// It is overwritten with the PolkaVM blob before yielding the compiled project artifacts.
|
||||
/// Hence it will be the same as the runtime code but we keep both for compatibility reasons.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deployed_bytecode: Option<DeployedBytecode>,
|
||||
/// The contract function signatures.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub method_identifiers: Option<BTreeMap<String, String>>,
|
||||
}
|
||||
|
||||
impl EVM {
|
||||
/// Sets the PolkaVM assembly and bytecode.
|
||||
pub fn modify(&mut self, assembly_text: String, bytecode: String) {
|
||||
self.assembly_text = Some(assembly_text);
|
||||
self.bytecode = Some(Bytecode::new(bytecode.clone()));
|
||||
self.deployed_bytecode = Some(DeployedBytecode::new(bytecode));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//! The `solc --standard-json` output contract.
|
||||
|
||||
pub mod evm;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::evm::EVM;
|
||||
|
||||
/// The `solc --standard-json` output contract.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Contract {
|
||||
/// The contract ABI.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub abi: Option<serde_json::Value>,
|
||||
/// The contract metadata.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
/// The contract developer documentation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub devdoc: Option<serde_json::Value>,
|
||||
/// The contract user documentation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub userdoc: Option<serde_json::Value>,
|
||||
/// The contract storage layout.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub storage_layout: Option<serde_json::Value>,
|
||||
/// Contract's bytecode and related objects
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub evm: Option<EVM>,
|
||||
/// The contract IR code.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ir: Option<String>,
|
||||
/// The contract optimized IR code.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ir_optimized: Option<String>,
|
||||
/// The contract PolkaVM bytecode hash.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub hash: Option<String>,
|
||||
/// The contract factory dependencies.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub factory_dependencies: Option<BTreeMap<String, String>>,
|
||||
/// The contract missing libraries.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub missing_libraries: Option<HashSet<String>>,
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//! The `solc --standard-json` output error.
|
||||
|
||||
pub mod source_location;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::source_location::SourceLocation;
|
||||
|
||||
/// The `solc --standard-json` output error.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Error {
|
||||
/// The component type.
|
||||
pub component: String,
|
||||
/// The error code.
|
||||
pub error_code: Option<String>,
|
||||
/// The formatted error message.
|
||||
pub formatted_message: String,
|
||||
/// The non-formatted error message.
|
||||
pub message: String,
|
||||
/// The error severity.
|
||||
pub severity: String,
|
||||
/// The error location data.
|
||||
pub source_location: Option<SourceLocation>,
|
||||
/// The error type.
|
||||
pub r#type: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Returns the `ecrecover` function usage warning.
|
||||
pub fn message_ecrecover(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.
|
||||
Polkadot comes with native account abstraction support, therefore it is highly recommended NOT
|
||||
to rely on the fact that the account has an ECDSA private key attached to it since accounts might
|
||||
implement other signature schemes.
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `<address payable>`'s `send` and `transfer` methods usage error.
|
||||
pub fn message_send_and_transfer(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
Warning: It looks like you are using '<address payable>.send/transfer(<X>)'.
|
||||
Using '<address payable>.send/transfer(<X>)' is deprecated and strongly discouraged!
|
||||
The resolc compiler uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls,
|
||||
which disables call re-entrancy and supplies all remaining gas instead of the 2300 gas stipend.
|
||||
However, detection is not guaranteed. You are advised to carefully test this, employ
|
||||
re-entrancy guards or use the withdrawal pattern instead!
|
||||
Learn more on https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy
|
||||
and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `extcodesize` instruction usage warning.
|
||||
pub fn message_extcodesize(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is
|
||||
usually needed in the following cases:
|
||||
1. To detect whether an address belongs to a smart contract.
|
||||
2. To detect whether the deploy code execution has finished.
|
||||
Polkadot comes with native account abstraction support (so smart contracts are just accounts
|
||||
coverned by code), and you should avoid differentiating between contracts and non-contract
|
||||
addresses.
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `origin` instruction usage warning.
|
||||
pub fn message_tx_origin(src: Option<&str>) -> Self {
|
||||
let message = r#"
|
||||
Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior.
|
||||
Polkadot comes with native account abstraction support, and therefore the initiator of a
|
||||
transaction might be different from the contract calling your code. It is highly recommended NOT
|
||||
to rely on tx.origin, but use msg.sender instead.
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends the contract path to the message..
|
||||
pub fn push_contract_path(&mut self, path: &str) {
|
||||
self.formatted_message
|
||||
.push_str(format!("\n--> {path}\n").as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.formatted_message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//! The `solc --standard-json` output error source location.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The `solc --standard-json` output error source location.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SourceLocation {
|
||||
/// The source file path.
|
||||
pub file: String,
|
||||
/// The start location.
|
||||
pub start: isize,
|
||||
/// The end location.
|
||||
pub end: isize,
|
||||
}
|
||||
|
||||
impl FromStr for SourceLocation {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
let mut parts = string.split(':');
|
||||
let start = parts
|
||||
.next()
|
||||
.map(|string| string.parse::<isize>())
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or_default();
|
||||
let length = parts
|
||||
.next()
|
||||
.map(|string| string.parse::<isize>())
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or_default();
|
||||
let file = parts.next().unwrap_or_default().to_owned();
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
start,
|
||||
end: start + length,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//! The `solc --standard-json` output.
|
||||
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
pub mod source;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::warning::Warning;
|
||||
|
||||
use self::contract::Contract;
|
||||
use self::error::Error as SolcStandardJsonOutputError;
|
||||
use self::source::Source;
|
||||
|
||||
/// The `solc --standard-json` output.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct Output {
|
||||
/// The file-contract hashmap.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub contracts: Option<BTreeMap<String, BTreeMap<String, Contract>>>,
|
||||
/// The source code mapping data.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sources: Option<BTreeMap<String, Source>>,
|
||||
/// The compilation errors and warnings.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub errors: Option<Vec<SolcStandardJsonOutputError>>,
|
||||
/// The `solc` compiler version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
/// The `solc` compiler long version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub long_version: Option<String>,
|
||||
/// The `resolc` compiler version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub revive_version: Option<String>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
/// Traverses the AST and returns the list of additional errors and warnings.
|
||||
#[cfg(feature = "resolc")]
|
||||
pub fn preprocess_ast(&mut self, suppressed_warnings: &[Warning]) -> anyhow::Result<()> {
|
||||
let sources = match self.sources.as_ref() {
|
||||
Some(sources) => sources,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let mut messages = Vec::new();
|
||||
for (path, source) in sources.iter() {
|
||||
if let Some(ast) = source.ast.as_ref() {
|
||||
let mut polkavm_messages = Source::get_messages(ast, suppressed_warnings);
|
||||
for message in polkavm_messages.iter_mut() {
|
||||
message.push_contract_path(path.as_str());
|
||||
}
|
||||
messages.extend(polkavm_messages);
|
||||
}
|
||||
}
|
||||
self.errors = match self.errors.take() {
|
||||
Some(mut errors) => {
|
||||
errors.extend(messages);
|
||||
Some(errors)
|
||||
}
|
||||
None => Some(messages),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
//! The `solc --standard-json` output source.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::standard_json::output::error::Error as SolcStandardJsonOutputError;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::warning::Warning;
|
||||
|
||||
/// The `solc --standard-json` output source.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
/// The source code ID.
|
||||
pub id: usize,
|
||||
/// The source code AST.
|
||||
pub ast: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
/// Checks the AST node for the `ecrecover` function usage.
|
||||
pub fn check_ecrecover(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "FunctionCall" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
if expression.get("nodeType")?.as_str()? != "Identifier" {
|
||||
return None;
|
||||
}
|
||||
if expression.get("name")?.as_str()? != "ecrecover" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_ecrecover(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks the AST node for the `<address payable>`'s `send` and `transfer` methods usage.
|
||||
pub fn check_send_and_transfer(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "FunctionCall" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
if expression.get("nodeType")?.as_str()? != "MemberAccess" {
|
||||
return None;
|
||||
}
|
||||
let member_name = expression.get("memberName")?.as_str()?;
|
||||
if member_name != "send" && member_name != "transfer" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_send_and_transfer(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks the AST node for the `extcodesize` assembly instruction usage.
|
||||
pub fn check_assembly_extcodesize(
|
||||
ast: &serde_json::Value,
|
||||
) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "YulFunctionCall" {
|
||||
return None;
|
||||
}
|
||||
if ast
|
||||
.get("functionName")?
|
||||
.as_object()?
|
||||
.get("name")?
|
||||
.as_str()?
|
||||
!= "extcodesize"
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_extcodesize(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks the AST node for the `origin` assembly instruction usage.
|
||||
pub fn check_assembly_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "YulFunctionCall" {
|
||||
return None;
|
||||
}
|
||||
if ast
|
||||
.get("functionName")?
|
||||
.as_object()?
|
||||
.get("name")?
|
||||
.as_str()?
|
||||
!= "origin"
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_tx_origin(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks the AST node for the `tx.origin` value usage.
|
||||
pub fn check_tx_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
let ast = ast.as_object()?;
|
||||
|
||||
if ast.get("nodeType")?.as_str()? != "MemberAccess" {
|
||||
return None;
|
||||
}
|
||||
if ast.get("memberName")?.as_str()? != "origin" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
if expression.get("nodeType")?.as_str()? != "Identifier" {
|
||||
return None;
|
||||
}
|
||||
if expression.get("name")?.as_str()? != "tx" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_tx_origin(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the list of messages for some specific parts of the AST.
|
||||
#[cfg(feature = "resolc")]
|
||||
pub fn get_messages(
|
||||
ast: &serde_json::Value,
|
||||
suppressed_warnings: &[Warning],
|
||||
) -> Vec<SolcStandardJsonOutputError> {
|
||||
let mut messages = Vec::new();
|
||||
if !suppressed_warnings.contains(&Warning::EcRecover) {
|
||||
if let Some(message) = Self::check_ecrecover(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if !suppressed_warnings.contains(&Warning::SendTransfer) {
|
||||
if let Some(message) = Self::check_send_and_transfer(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if !suppressed_warnings.contains(&Warning::ExtCodeSize) {
|
||||
if let Some(message) = Self::check_assembly_extcodesize(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if !suppressed_warnings.contains(&Warning::TxOrigin) {
|
||||
if let Some(message) = Self::check_assembly_origin(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
if let Some(message) = Self::check_tx_origin(ast) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
match ast {
|
||||
serde_json::Value::Array(array) => {
|
||||
for element in array.iter() {
|
||||
messages.extend(Self::get_messages(element, suppressed_warnings));
|
||||
}
|
||||
}
|
||||
serde_json::Value::Object(object) => {
|
||||
for (_key, value) in object.iter() {
|
||||
messages.extend(Self::get_messages(value, suppressed_warnings));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
|
||||
/// Returns the name of the last contract.
|
||||
pub fn last_contract_name(&self) -> anyhow::Result<String> {
|
||||
self.ast
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("The AST is empty"))?
|
||||
.get("nodes")
|
||||
.and_then(|value| value.as_array())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("The last contract cannot be found in an empty list of nodes")
|
||||
})?
|
||||
.iter()
|
||||
.filter_map(
|
||||
|node| match node.get("nodeType").and_then(|node| node.as_str()) {
|
||||
Some("ContractDefinition") => Some(node.get("name")?.as_str()?.to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
)
|
||||
.last()
|
||||
.ok_or_else(|| anyhow::anyhow!("The last contract not found in the AST"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user