mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-05-07 17:48:02 +00:00
Separate compilation and linker phases (#376)
Separate between compilation and linker phases to allow deploy time linking and back-porting era compiler changes to fix #91. Unlinked contract binaries (caused by missing libraries or missing factory dependencies in turn) are emitted as raw ELF object. Few drive by fixes: - #98 - A compiler panic on missing libraries definitions. - Fixes some incosistent type forwarding in JSON output (empty string vs. null object). - Remove the unused fallback for size optimization setting. - Remove the broken `--lvm-ir` mode. - CI workflow fixes. --------- Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com> Signed-off-by: xermicus <bigcyrill@hotmail.com> Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
@@ -1,72 +1,64 @@
|
||||
//! The `solc --combined-json` contract.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The contract.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Contract {
|
||||
/// The `solc` hashes output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hashes: Option<BTreeMap<String, String>>,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub hashes: BTreeMap<String, String>,
|
||||
/// The `solc` ABI output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub abi: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub abi: serde_json::Value,
|
||||
/// The `solc` metadata output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<String>,
|
||||
/// The `solc` developer documentation output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub devdoc: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub devdoc: serde_json::Value,
|
||||
/// The `solc` user documentation output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub userdoc: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub userdoc: serde_json::Value,
|
||||
/// The `solc` storage layout output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub storage_layout: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub storage_layout: serde_json::Value,
|
||||
/// The `solc` AST output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ast: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub ast: serde_json::Value,
|
||||
/// The `solc` assembly output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub asm: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub asm: serde_json::Value,
|
||||
|
||||
/// LLVM-generated assembly.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none", skip_deserializing)]
|
||||
pub assembly: Option<String>,
|
||||
/// The `solc` hexadecimal binary output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none", skip_deserializing)]
|
||||
pub bin: Option<String>,
|
||||
/// The `solc` hexadecimal binary runtime part output.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none", skip_deserializing)]
|
||||
pub bin_runtime: Option<String>,
|
||||
/// The factory dependencies.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub factory_deps: Option<BTreeMap<String, String>>,
|
||||
/// The missing libraries.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub missing_libraries: Option<HashSet<String>>,
|
||||
}
|
||||
|
||||
impl Contract {
|
||||
/// Returns the signature hash of the specified contract entry.
|
||||
/// # Panics
|
||||
/// If the hashes have not been requested in the `solc` call.
|
||||
pub fn entry(&self, entry: &str) -> u32 {
|
||||
self.hashes
|
||||
.as_ref()
|
||||
.expect("Always exists")
|
||||
.iter()
|
||||
.find_map(|(contract_entry, hash)| {
|
||||
if contract_entry.starts_with(entry) {
|
||||
Some(
|
||||
u32::from_str_radix(hash.as_str(), revive_common::BASE_HEXADECIMAL)
|
||||
.expect("Test hash is always valid"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| panic!("Entry `{entry}` not found"))
|
||||
}
|
||||
/// The unlinked factory dependencies.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub factory_deps_unlinked: std::collections::BTreeSet<String>,
|
||||
/// The factory dependencies.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub factory_deps: BTreeMap<String, String>,
|
||||
/// The missing libraries.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub missing_libraries: std::collections::BTreeSet<String>,
|
||||
/// The binary object format.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub object_format: Option<revive_common::ObjectFormat>,
|
||||
}
|
||||
|
||||
@@ -1,79 +1,51 @@
|
||||
//! The `solc --combined-json` output.
|
||||
|
||||
pub mod contract;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::contract::Contract;
|
||||
|
||||
pub mod contract;
|
||||
pub mod selector;
|
||||
|
||||
/// The `solc --combined-json` output.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CombinedJson {
|
||||
/// The contract entries.
|
||||
pub contracts: BTreeMap<String, Contract>,
|
||||
/// The list of source files.
|
||||
#[serde(rename = "sourceList")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_list: Option<Vec<String>>,
|
||||
#[serde(default, rename = "sourceList", skip_serializing_if = "Vec::is_empty")]
|
||||
pub source_list: Vec<String>,
|
||||
/// The source code extra data, including the AST.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sources: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub sources: serde_json::Value,
|
||||
/// The `solc` compiler version.
|
||||
pub version: String,
|
||||
/// The `resolc` compiler version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub revive_version: Option<String>,
|
||||
#[cfg(feature = "resolc")]
|
||||
pub resolc_version: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
impl CombinedJson {
|
||||
/// Returns the signature hash of the specified contract and entry.
|
||||
pub fn entry(&self, path: &str, entry: &str) -> u32 {
|
||||
self.contracts
|
||||
.iter()
|
||||
.find_map(|(name, contract)| {
|
||||
if name.starts_with(path) {
|
||||
Some(contract)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.expect("Always exists")
|
||||
.entry(entry)
|
||||
}
|
||||
|
||||
/// Returns the full contract path which can be found in `combined-json` output.
|
||||
pub fn get_full_path(&self, name: &str) -> Option<String> {
|
||||
self.contracts.iter().find_map(|(path, _value)| {
|
||||
if let Some(last_slash_position) = path.rfind('/') {
|
||||
if let Some(colon_position) = path.rfind(':') {
|
||||
if &path[last_slash_position + 1..colon_position] == name {
|
||||
return Some(path.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Removes EVM artifacts to prevent their accidental usage.
|
||||
pub fn remove_evm(&mut self) {
|
||||
for (_, contract) in self.contracts.iter_mut() {
|
||||
contract.bin = None;
|
||||
contract.bin_runtime = None;
|
||||
/// A shortcut constructor.
|
||||
pub fn new(solc_version: semver::Version, resolc_version: Option<String>) -> Self {
|
||||
Self {
|
||||
contracts: BTreeMap::new(),
|
||||
source_list: Vec::new(),
|
||||
sources: serde_json::Value::Null,
|
||||
version: solc_version.to_string(),
|
||||
resolc_version,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the JSON to the specified directory.
|
||||
pub fn write_to_directory(
|
||||
self,
|
||||
output_directory: &Path,
|
||||
output_directory: &std::path::Path,
|
||||
overwrite: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut file_path = output_directory.to_owned();
|
||||
@@ -85,10 +57,11 @@ impl CombinedJson {
|
||||
);
|
||||
}
|
||||
|
||||
File::create(&file_path)
|
||||
.map_err(|error| anyhow::anyhow!("File {:?} creating error: {}", file_path, error))?
|
||||
.write_all(serde_json::to_vec(&self).expect("Always valid").as_slice())
|
||||
.map_err(|error| anyhow::anyhow!("File {:?} writing error: {}", file_path, error))?;
|
||||
std::fs::write(
|
||||
file_path.as_path(),
|
||||
serde_json::to_vec(&self).expect("Always valid").as_slice(),
|
||||
)
|
||||
.map_err(|error| anyhow::anyhow!("File {file_path:?} writing: {error}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
//! The `solc --combined-json` expected output selection flag.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The solc `--combind-json` invalid selector message.
|
||||
pub const MESSAGE_SELECTOR_INVALID: &str = "Invalid option to --combined-json";
|
||||
|
||||
/// The `solc --combined-json` expected output selection flag.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Selector {
|
||||
/// The ABI JSON.
|
||||
#[serde(rename = "abi")]
|
||||
ABI,
|
||||
/// The function signature hashes JSON.
|
||||
#[serde(rename = "hashes")]
|
||||
Hashes,
|
||||
/// The metadata.
|
||||
#[serde(rename = "metadata")]
|
||||
Metadata,
|
||||
/// The developer documentation.
|
||||
#[serde(rename = "devdoc")]
|
||||
Devdoc,
|
||||
/// The user documentation.
|
||||
#[serde(rename = "userdoc")]
|
||||
Userdoc,
|
||||
/// The storage layout.
|
||||
#[serde(rename = "storage-layout")]
|
||||
StorageLayout,
|
||||
/// The transient storage layout.
|
||||
#[serde(rename = "transient-storage-layout")]
|
||||
TransientStorageLayout,
|
||||
/// The AST JSON.
|
||||
#[serde(rename = "ast")]
|
||||
AST,
|
||||
/// The EVM assembly.
|
||||
#[serde(rename = "asm")]
|
||||
ASM,
|
||||
|
||||
/// The assembly.
|
||||
#[serde(rename = "assembly", skip_serializing)]
|
||||
Assembly,
|
||||
|
||||
/// The deploy bytecode.
|
||||
#[serde(rename = "bin", skip_serializing)]
|
||||
Bytecode,
|
||||
/// The runtime bytecode.
|
||||
#[serde(rename = "bin-runtime", skip_serializing)]
|
||||
BytecodeRuntime,
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
/// Converts the comma-separated CLI argument into an array of flags.
|
||||
pub fn from_cli(format: &str) -> Vec<anyhow::Result<Self>> {
|
||||
format
|
||||
.split(',')
|
||||
.map(|flag| Self::from_str(flag.trim()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether the selector is available in `solc`.
|
||||
pub fn is_source_solc(&self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
Self::Assembly | Self::Bytecode | Self::BytecodeRuntime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Selector {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"abi" => Ok(Self::ABI),
|
||||
"hashes" => Ok(Self::Hashes),
|
||||
"metadata" => Ok(Self::Metadata),
|
||||
"devdoc" => Ok(Self::Devdoc),
|
||||
"userdoc" => Ok(Self::Userdoc),
|
||||
"storage-layout" => Ok(Self::StorageLayout),
|
||||
"transient-storage-layout" => Ok(Self::TransientStorageLayout),
|
||||
"ast" => Ok(Self::AST),
|
||||
"asm" => Ok(Self::ASM),
|
||||
|
||||
"bin" => Ok(Self::Bytecode),
|
||||
"bin-runtime" => Ok(Self::BytecodeRuntime),
|
||||
|
||||
"assembly" => Ok(Self::Assembly),
|
||||
|
||||
selector => anyhow::bail!("{MESSAGE_SELECTOR_INVALID}: {selector}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Selector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ABI => write!(f, "abi"),
|
||||
Self::Hashes => write!(f, "hashes"),
|
||||
Self::Metadata => write!(f, "metadata"),
|
||||
Self::Devdoc => write!(f, "devdoc"),
|
||||
Self::Userdoc => write!(f, "userdoc"),
|
||||
Self::StorageLayout => write!(f, "storage-layout"),
|
||||
Self::TransientStorageLayout => write!(f, "transient-storage-layout"),
|
||||
Self::AST => write!(f, "ast"),
|
||||
Self::ASM => write!(f, "asm"),
|
||||
|
||||
Self::Bytecode => write!(f, "bin"),
|
||||
Self::BytecodeRuntime => write!(f, "bin-runtime"),
|
||||
|
||||
Self::Assembly => write!(f, "assembly"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,13 @@
|
||||
//!
|
||||
//! [0]: https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description
|
||||
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
pub use self::combined_json::contract::Contract as CombinedJsonContract;
|
||||
pub use self::combined_json::selector::Selector as CombinedJsonSelector;
|
||||
pub use self::combined_json::selector::MESSAGE_SELECTOR_INVALID as CombinedJsonInvalidSelectorMessage;
|
||||
pub use self::standard_json::input::language::Language as SolcStandardJsonInputLanguage;
|
||||
pub use self::standard_json::input::settings::libraries::Libraries as SolcStandardJsonInputSettingsLibraries;
|
||||
pub use self::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
|
||||
pub use self::standard_json::input::settings::metadata_hash::MetadataHash as SolcStandardJsonInputSettingsMetadataHash;
|
||||
pub use self::standard_json::input::settings::optimizer::yul_details::YulDetails as SolcStandardJsonInputSettingsYulOptimizerDetails;
|
||||
@@ -16,17 +21,19 @@ pub use self::standard_json::input::settings::polkavm::PolkaVM as SolcStandardJs
|
||||
pub use self::standard_json::input::settings::selection::file::flag::Flag as SolcStandardJsonInputSettingsSelectionFileFlag;
|
||||
pub use self::standard_json::input::settings::selection::file::File as SolcStandardJsonInputSettingsSelectionFile;
|
||||
pub use self::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
|
||||
#[cfg(feature = "resolc")]
|
||||
pub use self::standard_json::input::settings::warning::Warning as ResolcWarning;
|
||||
pub use self::standard_json::input::settings::Settings as SolcStandardJsonInputSettings;
|
||||
pub use self::standard_json::input::source::Source as SolcStandardJsonInputSource;
|
||||
pub use self::standard_json::input::Input as SolcStandardJsonInput;
|
||||
pub use self::standard_json::output::contract::evm::bytecode::Bytecode as SolcStandardJsonOutputContractEVMBytecode;
|
||||
pub use self::standard_json::output::contract::evm::EVM as SolcStandardJsonOutputContractEVM;
|
||||
pub use self::standard_json::output::contract::Contract as SolcStandardJsonOutputContract;
|
||||
pub use self::standard_json::output::Output as SolcStandardJsonOutput;
|
||||
#[cfg(feature = "resolc")]
|
||||
pub use self::warning::Warning as ResolcWarning;
|
||||
pub use self::standard_json::output::error::error_handler::ErrorHandler as SolcStandardJsonOutputErrorHandler;
|
||||
pub use self::standard_json::output::error::mapped_location::MappedLocation as SolcStandardJsonOutputErrorMappedLocation;
|
||||
pub use self::standard_json::output::error::Error as SolcStandardJsonOutputError;
|
||||
pub use self::standard_json::output::Output as SolcStandardJsonOutput;
|
||||
|
||||
pub mod combined_json;
|
||||
pub mod standard_json;
|
||||
#[cfg(feature = "resolc")]
|
||||
pub mod warning;
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
//! The `solc --standard-json` input.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
#[cfg(feature = "resolc")]
|
||||
use std::collections::BTreeSet;
|
||||
#[cfg(feature = "resolc")]
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "resolc")]
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(all(feature = "parallel", feature = "resolc"))]
|
||||
use rayon::iter::{IntoParallelIterator, IntoParallelRefMutIterator, ParallelIterator};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::SolcStandardJsonInputSettingsLibraries;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::SolcStandardJsonInputSettingsPolkaVM;
|
||||
|
||||
use self::language::Language;
|
||||
#[cfg(feature = "resolc")]
|
||||
use self::settings::warning::Warning;
|
||||
use self::settings::Settings;
|
||||
use self::source::Source;
|
||||
|
||||
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 crate::SolcStandardJsonInputSettingsPolkaVM;
|
||||
|
||||
use self::language::Language;
|
||||
use self::settings::Settings;
|
||||
use self::source::Source;
|
||||
|
||||
/// The `solc --standard-json` input.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -36,93 +46,88 @@ pub struct Input {
|
||||
pub settings: Settings,
|
||||
/// The suppressed warnings.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(skip_serializing)]
|
||||
pub suppressed_warnings: Option<Vec<Warning>>,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub suppressed_warnings: Vec<Warning>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
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.
|
||||
///
|
||||
/// If the `path` is `None`, the input is read from the stdin.
|
||||
pub fn try_from(path: Option<&Path>) -> anyhow::Result<Self> {
|
||||
let input_json = match path {
|
||||
Some(path) => std::fs::read_to_string(path)
|
||||
.map_err(|error| anyhow::anyhow!("Standard JSON file {path:?} reading: {error}")),
|
||||
None => std::io::read_to_string(std::io::stdin())
|
||||
.map_err(|error| anyhow::anyhow!("Standard JSON reading from stdin: {error}")),
|
||||
}?;
|
||||
revive_common::deserialize_from_str::<Self>(input_json.as_str())
|
||||
.map_err(|error| anyhow::anyhow!("Standard JSON parsing: {error}"))
|
||||
}
|
||||
|
||||
/// A shortcut constructor from paths.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn try_from_paths(
|
||||
language: Language,
|
||||
pub fn try_from_solidity_paths(
|
||||
evm_version: Option<revive_common::EVMVersion>,
|
||||
paths: &[PathBuf],
|
||||
library_map: Vec<String>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
libraries: &[String],
|
||||
remappings: BTreeSet<String>,
|
||||
output_selection: SolcStandardJsonInputSettingsSelection,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
|
||||
#[cfg(feature = "resolc")] suppressed_warnings: Option<Vec<Warning>>,
|
||||
polkavm: Option<SolcStandardJsonInputSettingsPolkaVM>,
|
||||
metadata: SolcStandardJsonInputSettingsMetadata,
|
||||
suppressed_warnings: Vec<Warning>,
|
||||
polkavm: SolcStandardJsonInputSettingsPolkaVM,
|
||||
llvm_arguments: Vec<String>,
|
||||
detect_missing_libraries: bool,
|
||||
) -> 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() {
|
||||
let libraries = SolcStandardJsonInputSettingsLibraries::try_from(libraries)?;
|
||||
for library_file in libraries.as_inner().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();
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = paths.into_par_iter(); // Parallel iterator
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let iter = paths.into_iter(); // Sequential iterator
|
||||
|
||||
Ok(Self {
|
||||
language,
|
||||
let sources = iter
|
||||
.map(|path| {
|
||||
let source = Source::try_read(path.as_path())?;
|
||||
Ok((path.to_string_lossy().to_string(), source))
|
||||
})
|
||||
.collect::<anyhow::Result<BTreeMap<String, Source>>>()?;
|
||||
|
||||
Self::try_from_solidity_sources(
|
||||
evm_version,
|
||||
sources,
|
||||
settings: Settings::new(
|
||||
evm_version,
|
||||
libraries,
|
||||
remappings,
|
||||
output_selection,
|
||||
optimizer,
|
||||
metadata,
|
||||
polkavm,
|
||||
),
|
||||
#[cfg(feature = "resolc")]
|
||||
libraries,
|
||||
remappings,
|
||||
output_selection,
|
||||
optimizer,
|
||||
metadata,
|
||||
suppressed_warnings,
|
||||
})
|
||||
polkavm,
|
||||
llvm_arguments,
|
||||
detect_missing_libraries,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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(
|
||||
pub fn try_from_solidity_sources(
|
||||
evm_version: Option<revive_common::EVMVersion>,
|
||||
sources: BTreeMap<String, String>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
sources: BTreeMap<String, Source>,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
remappings: BTreeSet<String>,
|
||||
output_selection: SolcStandardJsonInputSettingsSelection,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
|
||||
suppressed_warnings: Option<Vec<Warning>>,
|
||||
polkavm: Option<SolcStandardJsonInputSettingsPolkaVM>,
|
||||
metadata: SolcStandardJsonInputSettingsMetadata,
|
||||
suppressed_warnings: Vec<Warning>,
|
||||
polkavm: SolcStandardJsonInputSettingsPolkaVM,
|
||||
llvm_arguments: Vec<String>,
|
||||
detect_missing_libraries: bool,
|
||||
) -> 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,
|
||||
@@ -134,13 +139,75 @@ impl Input {
|
||||
optimizer,
|
||||
metadata,
|
||||
polkavm,
|
||||
suppressed_warnings.clone(),
|
||||
llvm_arguments,
|
||||
detect_missing_libraries,
|
||||
),
|
||||
suppressed_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the necessary defaults.
|
||||
pub fn normalize(&mut self) {
|
||||
self.settings.normalize();
|
||||
/// A shortcut constructor from paths to Yul source files.
|
||||
pub fn from_yul_paths(
|
||||
paths: &[PathBuf],
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
llvm_options: Vec<String>,
|
||||
) -> Self {
|
||||
let sources = paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
(
|
||||
path.to_string_lossy().to_string(),
|
||||
Source::from(path.as_path()),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Self::from_yul_sources(sources, libraries, optimizer, llvm_options)
|
||||
}
|
||||
|
||||
/// A shortcut constructor from Yul source code.
|
||||
pub fn from_yul_sources(
|
||||
sources: BTreeMap<String, Source>,
|
||||
libraries: SolcStandardJsonInputSettingsLibraries,
|
||||
optimizer: SolcStandardJsonInputSettingsOptimizer,
|
||||
llvm_arguments: Vec<String>,
|
||||
) -> Self {
|
||||
let output_selection = SolcStandardJsonInputSettingsSelection::new_yul_validation();
|
||||
|
||||
Self {
|
||||
language: Language::Yul,
|
||||
sources,
|
||||
settings: Settings::new(
|
||||
None,
|
||||
libraries,
|
||||
Default::default(),
|
||||
output_selection,
|
||||
optimizer,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
vec![],
|
||||
llvm_arguments,
|
||||
false,
|
||||
),
|
||||
suppressed_warnings: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the output selection with another one.
|
||||
pub fn extend_selection(&mut self, selection: SolcStandardJsonInputSettingsSelection) {
|
||||
self.settings.extend_selection(selection);
|
||||
}
|
||||
|
||||
/// Tries to resolve all sources.
|
||||
pub fn resolve_sources(&mut self) {
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = self.sources.par_iter_mut();
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let iter = self.sources.iter_mut();
|
||||
|
||||
iter.for_each(|(_path, source)| {
|
||||
let _ = source.try_resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
//! The Solidity libraries.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The Solidity libraries.
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct Libraries {
|
||||
/// The unified representation of libraries.
|
||||
#[serde(flatten)]
|
||||
pub inner: BTreeMap<String, BTreeMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Libraries {
|
||||
/// Returns a representation of libraries suitable for the LLD linker.
|
||||
pub fn as_linker_symbols(
|
||||
&self,
|
||||
) -> anyhow::Result<BTreeMap<String, [u8; revive_common::BYTE_LENGTH_ETH_ADDRESS]>> {
|
||||
let mut linker_symbols = BTreeMap::new();
|
||||
for (file, contracts) in self.inner.iter() {
|
||||
for (name, address) in contracts.iter() {
|
||||
let path = format!("{file}:{name}");
|
||||
|
||||
let address_stripped = address.strip_prefix("0x").unwrap_or(address.as_str());
|
||||
let address_vec = hex::decode(address_stripped).map_err(|error| {
|
||||
anyhow::anyhow!("Invalid address `{address}` of library `{path}`: {error}.")
|
||||
})?;
|
||||
let address_array: [u8; revive_common::BYTE_LENGTH_ETH_ADDRESS] = address_vec.try_into().map_err(|address_vec: Vec<u8>| {
|
||||
anyhow::anyhow!(
|
||||
"Incorrect size of address `{address}` of library `{path}`: expected {}, found {}.",
|
||||
revive_common::BYTE_LENGTH_ETH_ADDRESS,
|
||||
address_vec.len(),
|
||||
)
|
||||
})?;
|
||||
|
||||
linker_symbols.insert(path, address_array);
|
||||
}
|
||||
}
|
||||
Ok(linker_symbols)
|
||||
}
|
||||
|
||||
/// Returns a representation of libraries suitable for filtering.
|
||||
pub fn as_paths(&self) -> BTreeSet<String> {
|
||||
self.inner
|
||||
.iter()
|
||||
.flat_map(|(file, names)| {
|
||||
names
|
||||
.keys()
|
||||
.map(|name| format!("{file}:{name}"))
|
||||
.collect::<BTreeSet<String>>()
|
||||
})
|
||||
.collect::<BTreeSet<String>>()
|
||||
}
|
||||
|
||||
/// Checks whether the libraries are empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner value.
|
||||
pub fn as_inner(&self) -> &BTreeMap<String, BTreeMap<String, String>> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner value.
|
||||
pub fn as_inner_mut(&mut self) -> &mut BTreeMap<String, BTreeMap<String, String>> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BTreeMap<String, BTreeMap<String, String>>> for Libraries {
|
||||
fn from(inner: BTreeMap<String, BTreeMap<String, String>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[String]> for Libraries {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(arguments: &[String]) -> Result<Self, Self::Error> {
|
||||
let mut libraries = BTreeMap::new();
|
||||
for (index, library) in arguments.iter().enumerate() {
|
||||
let mut path_and_address = library.split('=');
|
||||
let path = path_and_address
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Library #{index} path is missing."))?;
|
||||
let mut file_and_contract = path.split(':');
|
||||
let file = file_and_contract
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Library `{path}` file name is missing."))?;
|
||||
let contract = file_and_contract
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Library `{path}` contract name is missing."))?;
|
||||
let address = path_and_address
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Library `{path}` address is missing."))?;
|
||||
libraries
|
||||
.entry(file.to_owned())
|
||||
.or_insert_with(BTreeMap::new)
|
||||
.insert(contract.to_owned(), address.to_owned());
|
||||
}
|
||||
Ok(Self { inner: libraries })
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,26 @@
|
||||
//! The `solc --standard-json` input settings.
|
||||
|
||||
pub mod libraries;
|
||||
pub mod metadata;
|
||||
pub mod metadata_hash;
|
||||
pub mod optimizer;
|
||||
pub mod polkavm;
|
||||
pub mod selection;
|
||||
#[cfg(feature = "resolc")]
|
||||
pub mod warning;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::libraries::Libraries;
|
||||
use self::metadata::Metadata;
|
||||
use self::optimizer::Optimizer;
|
||||
use self::polkavm::PolkaVM;
|
||||
use self::selection::Selection;
|
||||
#[cfg(feature = "resolc")]
|
||||
use self::warning::Warning;
|
||||
|
||||
/// The `solc --standard-json` input settings.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -25,14 +30,14 @@ pub struct Settings {
|
||||
#[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>>>,
|
||||
#[serde(default, skip_serializing_if = "Libraries::is_empty")]
|
||||
pub libraries: Libraries,
|
||||
/// The sorted list of remappings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remappings: Option<BTreeSet<String>>,
|
||||
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
|
||||
pub remappings: BTreeSet<String>,
|
||||
/// The output selection filters.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_selection: Option<Selection>,
|
||||
#[serde(default)]
|
||||
pub output_selection: Selection,
|
||||
/// Whether to compile via IR. Only for testing with solc >=0.8.13.
|
||||
#[serde(
|
||||
rename = "viaIR",
|
||||
@@ -43,67 +48,68 @@ pub struct Settings {
|
||||
/// The optimizer settings.
|
||||
pub optimizer: Optimizer,
|
||||
/// The metadata settings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<Metadata>,
|
||||
#[serde(default)]
|
||||
pub metadata: Metadata,
|
||||
/// The resolc custom PolkaVM settings.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub polkavm: Option<PolkaVM>,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub polkavm: PolkaVM,
|
||||
|
||||
/// The suppressed warnings.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(default, skip_serializing)]
|
||||
pub suppressed_warnings: Vec<self::warning::Warning>,
|
||||
|
||||
/// The extra LLVM arguments.
|
||||
#[cfg(feature = "resolc")]
|
||||
#[serde(default, alias = "LLVMOptions", skip_serializing)]
|
||||
pub llvm_arguments: Vec<String>,
|
||||
|
||||
/// Whether to enable the missing libraries detection mode.
|
||||
/// Deprecated in favor of post-compile-time linking.
|
||||
#[serde(default, rename = "detectMissingLibraries", skip_serializing)]
|
||||
pub detect_missing_libraries: bool,
|
||||
}
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
impl Settings {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
evm_version: Option<revive_common::EVMVersion>,
|
||||
libraries: BTreeMap<String, BTreeMap<String, String>>,
|
||||
remappings: Option<BTreeSet<String>>,
|
||||
libraries: Libraries,
|
||||
remappings: BTreeSet<String>,
|
||||
output_selection: Selection,
|
||||
optimizer: Optimizer,
|
||||
metadata: Option<Metadata>,
|
||||
polkavm: Option<PolkaVM>,
|
||||
metadata: Metadata,
|
||||
polkavm: PolkaVM,
|
||||
suppressed_warnings: Vec<Warning>,
|
||||
llvm_arguments: Vec<String>,
|
||||
detect_missing_libraries: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
evm_version,
|
||||
libraries: Some(libraries),
|
||||
libraries,
|
||||
remappings,
|
||||
output_selection: Some(output_selection),
|
||||
output_selection,
|
||||
optimizer,
|
||||
metadata,
|
||||
via_ir: Some(true),
|
||||
polkavm,
|
||||
suppressed_warnings,
|
||||
llvm_arguments,
|
||||
detect_missing_libraries,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the necessary defaults.
|
||||
pub fn normalize(&mut self) {
|
||||
self.polkavm = None;
|
||||
self.optimizer.normalize();
|
||||
/// Extends the output selection with another one.
|
||||
pub fn extend_selection(&mut self, selection: Selection) {
|
||||
self.output_selection.extend(selection);
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// Returns flags that are going to be automatically added by the compiler,
|
||||
/// but were not explicitly requested by the user.
|
||||
///
|
||||
/// Afterwards, the flags are used to prune JSON output before returning it.
|
||||
pub fn selection_to_prune(&self) -> Selection {
|
||||
self.output_selection.selection_to_prune()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use serde::Serialize;
|
||||
use crate::standard_json::input::settings::optimizer::yul_details::YulDetails;
|
||||
|
||||
/// The `solc --standard-json` input settings optimizer details.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Details {
|
||||
/// Whether the pass is enabled.
|
||||
@@ -40,7 +40,6 @@ pub struct Details {
|
||||
|
||||
impl Details {
|
||||
/// A shortcut constructor.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
peephole: Option<bool>,
|
||||
inliner: Option<bool>,
|
||||
|
||||
@@ -15,35 +15,31 @@ pub struct Optimizer {
|
||||
/// Whether the optimizer is enabled.
|
||||
pub enabled: bool,
|
||||
/// The optimization mode string.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mode: Option<char>,
|
||||
#[serde(default = "Optimizer::default_mode", skip_serializing)]
|
||||
pub mode: 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_if = "Option::is_none")]
|
||||
pub fallback_to_optimizing_for_size: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub details: Details,
|
||||
}
|
||||
|
||||
impl Optimizer {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
enabled: bool,
|
||||
mode: Option<char>,
|
||||
version: &semver::Version,
|
||||
fallback_to_optimizing_for_size: bool,
|
||||
) -> Self {
|
||||
pub fn new(enabled: bool, mode: char, details: Details) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
mode,
|
||||
details: Some(Details::disabled(version)),
|
||||
fallback_to_optimizing_for_size: Some(fallback_to_optimizing_for_size),
|
||||
details,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the necessary defaults.
|
||||
pub fn normalize(&mut self) {
|
||||
self.mode = None;
|
||||
self.fallback_to_optimizing_for_size = None;
|
||||
/// The default optimization mode.
|
||||
pub fn default_mode() -> char {
|
||||
'z'
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Optimizer {
|
||||
fn default() -> Self {
|
||||
Self::new(true, Self::default_mode(), Details::default())
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The `solc --standard-json` input settings optimizer YUL details.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct YulDetails {
|
||||
/// Whether the stack allocation pass is enabled.
|
||||
|
||||
+23
-19
@@ -30,6 +30,9 @@ pub enum Flag {
|
||||
/// The Yul IR.
|
||||
#[serde(rename = "irOptimized")]
|
||||
Yul,
|
||||
/// The EVM bytecode.
|
||||
#[serde(rename = "evm")]
|
||||
EVM,
|
||||
/// The EVM legacy assembly JSON.
|
||||
#[serde(rename = "evm.legacyAssembly")]
|
||||
EVMLA,
|
||||
@@ -45,22 +48,23 @@ pub enum Flag {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
//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::EVM => write!(f, "evm"),
|
||||
// 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"),
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -13,37 +13,56 @@ use self::flag::Flag as SelectionFlag;
|
||||
#[derive(Clone, 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>>,
|
||||
#[serde(default, rename = "", skip_serializing_if = "HashSet::is_empty")]
|
||||
pub per_file: HashSet<SelectionFlag>,
|
||||
/// The per-contract output selections.
|
||||
#[serde(rename = "*", skip_serializing_if = "Option::is_none")]
|
||||
pub per_contract: Option<HashSet<SelectionFlag>>,
|
||||
#[serde(default, rename = "*", skip_serializing_if = "HashSet::is_empty")]
|
||||
pub per_contract: HashSet<SelectionFlag>,
|
||||
}
|
||||
|
||||
impl File {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(flags: Vec<SelectionFlag>) -> Self {
|
||||
let mut per_file = HashSet::new();
|
||||
let mut per_contract = HashSet::new();
|
||||
for flag in flags.into_iter() {
|
||||
match flag {
|
||||
SelectionFlag::AST => {
|
||||
per_file.insert(SelectionFlag::AST);
|
||||
}
|
||||
flag => {
|
||||
per_contract.insert(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self {
|
||||
per_file,
|
||||
per_contract,
|
||||
}
|
||||
}
|
||||
/// Creates the selection required for production compilation (excludes EVM bytecode).
|
||||
pub fn new_required() -> Self {
|
||||
Self {
|
||||
per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
|
||||
per_contract: Some(HashSet::from_iter([
|
||||
per_file: HashSet::from_iter([SelectionFlag::AST]),
|
||||
per_contract: HashSet::from_iter([
|
||||
SelectionFlag::MethodIdentifiers,
|
||||
SelectionFlag::Metadata,
|
||||
SelectionFlag::Yul,
|
||||
])),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the selection required for test compilation (includes EVM bytecode).
|
||||
pub fn new_required_for_tests() -> Self {
|
||||
Self {
|
||||
per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
|
||||
per_contract: Some(HashSet::from_iter([
|
||||
per_file: HashSet::from_iter([SelectionFlag::AST]),
|
||||
per_contract: HashSet::from_iter([
|
||||
SelectionFlag::EVMBC,
|
||||
SelectionFlag::EVMDBC,
|
||||
SelectionFlag::MethodIdentifiers,
|
||||
SelectionFlag::Metadata,
|
||||
SelectionFlag::Yul,
|
||||
])),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,48 +70,59 @@ impl File {
|
||||
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.per_file.extend(required.per_file);
|
||||
self.per_contract.extend(required.per_contract);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn production_excludes_evm_bytecode() {
|
||||
let selection = File::new_required();
|
||||
let per_contract = selection.per_contract.unwrap();
|
||||
|
||||
// Production should NOT include EVM bytecode flags
|
||||
assert!(!per_contract.contains(&SelectionFlag::EVMBC));
|
||||
assert!(!per_contract.contains(&SelectionFlag::EVMDBC));
|
||||
|
||||
// But should include other required flags
|
||||
assert!(per_contract.contains(&SelectionFlag::MethodIdentifiers));
|
||||
assert!(per_contract.contains(&SelectionFlag::Metadata));
|
||||
assert!(per_contract.contains(&SelectionFlag::Yul));
|
||||
/// Extends the output selection with another one.
|
||||
pub fn extend(&mut self, other: Self) -> &mut Self {
|
||||
self.per_file.extend(other.per_file);
|
||||
self.per_contract.extend(other.per_contract);
|
||||
self
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tests_include_evm_bytecode() {
|
||||
let selection = File::new_required_for_tests();
|
||||
let per_contract = selection.per_contract.unwrap();
|
||||
/// Returns flags that are going to be automatically added by the compiler,
|
||||
/// but were not explicitly requested by the user.
|
||||
///
|
||||
/// Afterwards, the flags are used to prune JSON output before returning it.
|
||||
pub fn selection_to_prune(&self) -> Self {
|
||||
let required_per_file = vec![SelectionFlag::AST];
|
||||
let required_per_contract = vec![
|
||||
SelectionFlag::MethodIdentifiers,
|
||||
SelectionFlag::Metadata,
|
||||
SelectionFlag::Yul,
|
||||
];
|
||||
|
||||
// Tests should include EVM bytecode flags
|
||||
assert!(per_contract.contains(&SelectionFlag::EVMBC));
|
||||
assert!(per_contract.contains(&SelectionFlag::EVMDBC));
|
||||
let mut unset_per_file = HashSet::with_capacity(required_per_file.len());
|
||||
let mut unset_per_contract = HashSet::with_capacity(required_per_contract.len());
|
||||
|
||||
// And should also include other required flags
|
||||
assert!(per_contract.contains(&SelectionFlag::MethodIdentifiers));
|
||||
assert!(per_contract.contains(&SelectionFlag::Metadata));
|
||||
assert!(per_contract.contains(&SelectionFlag::Yul));
|
||||
for flag in required_per_file {
|
||||
if !self.per_file.contains(&flag) {
|
||||
unset_per_file.insert(flag);
|
||||
}
|
||||
}
|
||||
for flag in required_per_contract {
|
||||
if !self.per_contract.contains(&flag) {
|
||||
unset_per_contract.insert(flag);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
per_file: unset_per_file,
|
||||
per_contract: unset_per_contract,
|
||||
}
|
||||
}
|
||||
/// Whether the flag is requested.
|
||||
pub fn contains(&self, flag: &SelectionFlag) -> bool {
|
||||
match flag {
|
||||
flag @ SelectionFlag::AST => self.per_file.contains(flag),
|
||||
flag => self.per_contract.contains(flag),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the selection is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.per_file.is_empty() && self.per_contract.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,24 +7,33 @@ use std::collections::BTreeMap;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::file::flag::Flag;
|
||||
use self::file::File as FileSelection;
|
||||
|
||||
/// The `solc --standard-json` output selection.
|
||||
#[derive(Clone, 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(default, rename = "*", skip_serializing_if = "FileSelection::is_empty")]
|
||||
all: FileSelection,
|
||||
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty", flatten)]
|
||||
pub files: BTreeMap<String, FileSelection>,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
/// Creates the selection with arbitrary flags.
|
||||
pub fn new(flags: Vec<Flag>) -> Self {
|
||||
Self {
|
||||
all: FileSelection::new(flags),
|
||||
files: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the selection required by our compilation process.
|
||||
pub fn new_required() -> Self {
|
||||
Self {
|
||||
all: Some(FileSelection::new_required()),
|
||||
all: FileSelection::new_required(),
|
||||
files: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -32,76 +41,35 @@ impl Selection {
|
||||
/// Creates the selection required for test compilation (includes EVM bytecode).
|
||||
pub fn new_required_for_tests() -> Self {
|
||||
Self {
|
||||
all: Some(FileSelection::new_required_for_tests()),
|
||||
all: FileSelection::new_required_for_tests(),
|
||||
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();
|
||||
}
|
||||
/// Creates the selection required by Yul validation process.
|
||||
pub fn new_yul_validation() -> Self {
|
||||
Self::new(vec![Flag::EVM])
|
||||
}
|
||||
|
||||
/// Extends the output selection with another one.
|
||||
pub fn extend(&mut self, other: Self) -> &mut Self {
|
||||
self.all.extend(other.all);
|
||||
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)
|
||||
/// Returns flags that are going to be automatically added by the compiler,
|
||||
/// but were not explicitly requested by the user.
|
||||
///
|
||||
/// Afterwards, the flags are used to prune JSON output before returning it.
|
||||
pub fn selection_to_prune(&self) -> Self {
|
||||
Self {
|
||||
all: self.all.selection_to_prune(),
|
||||
files: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
/// Whether the flag is requested.
|
||||
pub fn contains(&self, flag: &Flag) -> bool {
|
||||
self.all.contains(flag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//! `resolc` custom compiler warnings.
|
||||
//!
|
||||
//! The revive compiler adds warnings only applicable when compilng
|
||||
//! to the revive stack on Polkadot to the output.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::standard_json::output::error::source_location::SourceLocation;
|
||||
use crate::SolcStandardJsonInputSource;
|
||||
use crate::SolcStandardJsonOutputError;
|
||||
|
||||
// The `resolc` custom compiler warning.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Warning {
|
||||
/// The `<address payable>`'s `send` and `transfer` methods usage warning.
|
||||
SendAndTransfer,
|
||||
/// The `origin` instruction usage warning.
|
||||
TxOrigin,
|
||||
}
|
||||
|
||||
impl Warning {
|
||||
/// Converts string arguments into an array of warnings.
|
||||
pub fn try_from_strings(strings: &[String]) -> Result<Vec<Self>, anyhow::Error> {
|
||||
strings
|
||||
.iter()
|
||||
.map(|string| Self::from_str(string))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The displayed warning messages.
|
||||
pub fn as_message(&self) -> &'static str {
|
||||
match self {
|
||||
Self::SendAndTransfer => {
|
||||
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
|
||||
"#
|
||||
}
|
||||
Self::TxOrigin => {
|
||||
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.
|
||||
"#
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_error(
|
||||
&self,
|
||||
node: Option<&str>,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
) -> SolcStandardJsonOutputError {
|
||||
SolcStandardJsonOutputError::new_warning(
|
||||
self.as_message(),
|
||||
node.and_then(|node| SourceLocation::try_from_ast(node, id_paths)),
|
||||
Some(sources),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Warning {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"sendandtransfer" => Ok(Self::SendAndTransfer),
|
||||
"txorigin" => Ok(Self::TxOrigin),
|
||||
_ => Err(anyhow::anyhow!("Invalid warning: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Warning {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::SendAndTransfer => write!(f, "sendandtransfer"),
|
||||
Self::TxOrigin => write!(f, "txorigin"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The `solc --standard-json` input source.
|
||||
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -11,30 +11,90 @@ use serde::Serialize;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
/// The source code file content.
|
||||
pub content: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
/// The source file URLs.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub urls: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
/// Reads the source from the file system.
|
||||
pub fn try_read(path: &Path) -> anyhow::Result<Self> {
|
||||
let content = if path.to_string_lossy() == "-" {
|
||||
std::io::read_to_string(std::io::stdin())
|
||||
.map_err(|error| anyhow::anyhow!("<stdin> reading: {error}"))
|
||||
} else {
|
||||
std::fs::read_to_string(path)
|
||||
.map_err(|error| anyhow::anyhow!("File {path:?} reading: {error}"))
|
||||
}?;
|
||||
|
||||
Ok(Self {
|
||||
content: Some(content),
|
||||
urls: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to resolve the source code.
|
||||
///
|
||||
/// At the moment only one URL pointing to the file system is supported.
|
||||
pub fn try_resolve(&mut self) -> anyhow::Result<()> {
|
||||
match (self.content.as_ref(), self.urls.as_ref()) {
|
||||
(Some(_), None) => Ok(()),
|
||||
(None, Some(urls)) => {
|
||||
let mut errors = Vec::with_capacity(urls.len());
|
||||
for url in urls.iter() {
|
||||
let url_path = PathBuf::from(url);
|
||||
match Source::try_read(url_path.as_path()) {
|
||||
Ok(resolved) => {
|
||||
*self = resolved;
|
||||
break;
|
||||
}
|
||||
Err(error) => errors.push(error),
|
||||
}
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
anyhow::bail!(
|
||||
"{}",
|
||||
errors
|
||||
.into_iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
(Some(_), Some(_)) => anyhow::bail!("Both `content` and `urls` cannot be set."),
|
||||
(None, None) => anyhow::bail!("Either `content` or `urls` must be set."),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes ownership of the source code and returns it.
|
||||
pub fn take_content(&mut self) -> Option<String> {
|
||||
self.content.take()
|
||||
}
|
||||
|
||||
/// Returns the source code reference, if the source has been previously read or resolved.
|
||||
pub fn content(&self) -> Option<&str> {
|
||||
self.content.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Source {
|
||||
fn from(content: String) -> Self {
|
||||
Self { content }
|
||||
Self {
|
||||
content: Some(content),
|
||||
urls: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
impl From<&Path> for Source {
|
||||
fn from(path: &Path) -> Self {
|
||||
Self {
|
||||
content: None,
|
||||
urls: Some(vec![path.to_string_lossy().to_string()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! The `solc --standard-json` output contract EVM data.
|
||||
|
||||
pub mod bytecode;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
@@ -10,9 +8,11 @@ use serde::Serialize;
|
||||
use self::bytecode::Bytecode;
|
||||
use self::bytecode::DeployedBytecode;
|
||||
|
||||
pub mod bytecode;
|
||||
|
||||
/// The `solc --standard-json` output contract EVM data.
|
||||
/// It is replaced by PolkaVM data after compiling.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EVM {
|
||||
/// The contract PolkaVM assembly code.
|
||||
@@ -28,8 +28,8 @@ pub struct EVM {
|
||||
#[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>>,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub method_identifiers: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl EVM {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
pub mod evm;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -11,24 +11,27 @@ use serde::Serialize;
|
||||
use self::evm::EVM;
|
||||
|
||||
/// The `solc --standard-json` output contract.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Default, 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>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub abi: serde_json::Value,
|
||||
/// The contract metadata.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub metadata: serde_json::Value,
|
||||
/// The contract developer documentation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub devdoc: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub devdoc: serde_json::Value,
|
||||
/// The contract user documentation.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub userdoc: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub userdoc: serde_json::Value,
|
||||
/// The contract storage layout.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub storage_layout: Option<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub storage_layout: serde_json::Value,
|
||||
/// The contract storage layout.
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub transient_storage_layout: serde_json::Value,
|
||||
/// Contract's bytecode and related objects
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub evm: Option<EVM>,
|
||||
@@ -36,15 +39,39 @@ pub struct Contract {
|
||||
#[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>,
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub ir_optimized: String,
|
||||
/// The contract PolkaVM bytecode hash.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub hash: Option<String>,
|
||||
/// Unlinked factory dependencies.
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub factory_dependencies_unlinked: BTreeSet<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>>,
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub factory_dependencies: BTreeMap<String, String>,
|
||||
/// Missing linkable libraries.
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub missing_libraries: BTreeSet<String>,
|
||||
/// Binary object format.
|
||||
#[serde(default, skip_deserializing)]
|
||||
pub object_format: Option<revive_common::ObjectFormat>,
|
||||
}
|
||||
|
||||
impl Contract {
|
||||
/// Checks if all fields are unset or empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.abi.is_null()
|
||||
&& self.storage_layout.is_null()
|
||||
&& self.transient_storage_layout.is_null()
|
||||
&& self.metadata.is_null()
|
||||
&& self.devdoc.is_null()
|
||||
&& self.userdoc.is_null()
|
||||
&& self.ir_optimized.is_empty()
|
||||
&& self.evm.is_none()
|
||||
&& self.hash.is_none()
|
||||
&& self.factory_dependencies_unlinked.is_empty()
|
||||
&& self.factory_dependencies.is_empty()
|
||||
&& self.missing_libraries.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//! Unifies error handling between different Solidity compilers.
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use crate::standard_json::output::error::Error;
|
||||
|
||||
/// The Solidity compiler error handler trait.
|
||||
///
|
||||
/// This is implemented by entities that can collect and handle errors.
|
||||
pub trait ErrorHandler {
|
||||
/// Returns errors as a list.
|
||||
fn errors(&self) -> Vec<&Error>;
|
||||
|
||||
/// Extracts warnings from the list of messages.
|
||||
fn take_warnings(&mut self) -> Vec<Error>;
|
||||
|
||||
/// Checks if there is at least one error.
|
||||
fn has_errors(&self) -> bool {
|
||||
!self.errors().is_empty()
|
||||
}
|
||||
|
||||
/// Collects errors into one message and bails, if there is at least one error.
|
||||
fn check_errors(&self) -> anyhow::Result<()> {
|
||||
if !self.has_errors() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"{}",
|
||||
self.errors()
|
||||
.iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks for errors, exiting the application if there is at least one error.
|
||||
fn exit_on_error(&self) {
|
||||
if !self.has_errors() {
|
||||
return;
|
||||
}
|
||||
|
||||
std::io::stderr()
|
||||
.write_all(
|
||||
self.errors()
|
||||
.iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
.as_bytes(),
|
||||
)
|
||||
.expect("Stderr writing error");
|
||||
std::process::exit(revive_common::EXIT_CODE_FAILURE);
|
||||
}
|
||||
|
||||
/// Removes warnings from the list of messages and prints them to stderr.
|
||||
fn take_and_write_warnings(&mut self) {
|
||||
let warnings = self.take_warnings();
|
||||
if warnings.is_empty() {
|
||||
return;
|
||||
}
|
||||
writeln!(
|
||||
std::io::stderr(),
|
||||
"{}",
|
||||
warnings
|
||||
.into_iter()
|
||||
.map(|error| error.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
)
|
||||
.expect("Stderr writing error");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
//! The mapped error location.
|
||||
|
||||
use crate::standard_json::output::error::source_location::SourceLocation;
|
||||
|
||||
/// The mapped error location.
|
||||
///
|
||||
/// It can be resolved from `solc` AST error location if the source code is provided.
|
||||
#[derive(Debug)]
|
||||
pub struct MappedLocation<'a> {
|
||||
/// The source file path.
|
||||
pub path: String,
|
||||
/// The line number.
|
||||
pub line: Option<usize>,
|
||||
/// The column number.
|
||||
pub column: Option<usize>,
|
||||
/// The error area length.
|
||||
pub length: Option<usize>,
|
||||
/// The source code line to print.
|
||||
pub source_code_line: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> MappedLocation<'a> {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(path: String) -> Self {
|
||||
Self {
|
||||
path,
|
||||
line: None,
|
||||
column: None,
|
||||
length: None,
|
||||
source_code_line: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A shortcut constructor.
|
||||
pub fn new_with_location(
|
||||
path: String,
|
||||
line: usize,
|
||||
column: usize,
|
||||
length: usize,
|
||||
source_code_line: Option<&'a str>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path,
|
||||
line: Some(line),
|
||||
column: Some(column),
|
||||
length: Some(length),
|
||||
source_code_line,
|
||||
}
|
||||
}
|
||||
|
||||
/// A shortcut constructor from `solc` AST source location.
|
||||
pub fn try_from_source_location(
|
||||
source_location: &SourceLocation,
|
||||
source_code: Option<&'a str>,
|
||||
) -> Self {
|
||||
let source_code = match source_code {
|
||||
Some(source_code) => source_code,
|
||||
None => return Self::new(source_location.file.to_owned()),
|
||||
};
|
||||
if source_location.start <= 0 || source_location.end <= 0 {
|
||||
return Self::new(source_location.file.to_owned());
|
||||
}
|
||||
let start = source_location.start as usize;
|
||||
let end = source_location.end as usize;
|
||||
|
||||
let mut cursor = 1;
|
||||
for (line, source_line) in source_code.lines().enumerate() {
|
||||
let cursor_next = cursor + source_line.len() + 1;
|
||||
|
||||
if cursor <= start && start <= cursor_next {
|
||||
let line = line + 1;
|
||||
let column = start - cursor;
|
||||
let length = end - start;
|
||||
return Self::new_with_location(
|
||||
source_location.file.to_owned(),
|
||||
line,
|
||||
column,
|
||||
length,
|
||||
Some(source_line),
|
||||
);
|
||||
}
|
||||
|
||||
cursor = cursor_next;
|
||||
}
|
||||
|
||||
Self::new(source_location.file.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MappedLocation<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let mut path = self.path.clone();
|
||||
if let Some(line) = self.line {
|
||||
path.push(':');
|
||||
path.push_str(line.to_string().as_str());
|
||||
if let Some(column) = self.column {
|
||||
path.push(':');
|
||||
path.push_str(column.to_string().as_str());
|
||||
if let (Some(source_code_line), Some(length)) = (self.source_code_line, self.length)
|
||||
{
|
||||
let line_number_length = line.to_string().len();
|
||||
writeln!(f, "{} --> {path}", " ".repeat(line_number_length))?;
|
||||
writeln!(f, " {} |", " ".repeat(line_number_length))?;
|
||||
writeln!(f, " {line} | {source_code_line}")?;
|
||||
writeln!(
|
||||
f,
|
||||
" {} | {} {}",
|
||||
" ".repeat(line_number_length),
|
||||
" ".repeat(column),
|
||||
"^".repeat(std::cmp::min(length, source_code_line.len() - column))
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
writeln!(f, "--> {path}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
//! The `solc --standard-json` output error.
|
||||
|
||||
pub mod source_location;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::SolcStandardJsonInputSource;
|
||||
|
||||
use self::mapped_location::MappedLocation;
|
||||
use self::source_location::SourceLocation;
|
||||
|
||||
pub mod error_handler;
|
||||
pub mod mapped_location;
|
||||
pub mod source_location;
|
||||
|
||||
/// The `solc --standard-json` output error.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -30,29 +35,81 @@ pub struct Error {
|
||||
}
|
||||
|
||||
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();
|
||||
/// The list of ignored `solc` warnings that are strictly EVM-related.
|
||||
pub const IGNORED_WARNING_CODES: [&'static str; 5] = ["1699", "3860", "5159", "5574", "6417"];
|
||||
|
||||
/// A shortcut constructor.
|
||||
pub fn new<S>(
|
||||
r#type: &str,
|
||||
message: S,
|
||||
source_location: Option<SourceLocation>,
|
||||
sources: Option<&BTreeMap<String, SolcStandardJsonInputSource>>,
|
||||
) -> Self
|
||||
where
|
||||
S: std::fmt::Display,
|
||||
{
|
||||
let message = message.to_string();
|
||||
|
||||
let message_trimmed = message.trim();
|
||||
let mut formatted_message = if message_trimmed.starts_with(r#type) {
|
||||
message_trimmed.to_owned()
|
||||
} else {
|
||||
format!("{type}: {message_trimmed}")
|
||||
};
|
||||
formatted_message.push('\n');
|
||||
if let Some(ref source_location) = source_location {
|
||||
let source_code = sources.and_then(|sources| {
|
||||
sources
|
||||
.get(source_location.file.as_str())
|
||||
.and_then(|source| source.content())
|
||||
});
|
||||
let mapped_location =
|
||||
MappedLocation::try_from_source_location(source_location, source_code);
|
||||
formatted_message.push_str(mapped_location.to_string().as_str());
|
||||
formatted_message.push('\n');
|
||||
}
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
formatted_message,
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
severity: r#type.to_lowercase(),
|
||||
source_location,
|
||||
r#type: r#type.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A shortcut constructor.
|
||||
pub fn new_error<S>(
|
||||
message: S,
|
||||
source_location: Option<SourceLocation>,
|
||||
sources: Option<&BTreeMap<String, SolcStandardJsonInputSource>>,
|
||||
) -> Self
|
||||
where
|
||||
S: std::fmt::Display,
|
||||
{
|
||||
Self::new("Error", message, source_location, sources)
|
||||
}
|
||||
|
||||
/// A shortcut constructor.
|
||||
pub fn new_warning<S>(
|
||||
message: S,
|
||||
source_location: Option<SourceLocation>,
|
||||
sources: Option<&BTreeMap<String, SolcStandardJsonInputSource>>,
|
||||
) -> Self
|
||||
where
|
||||
S: std::fmt::Display,
|
||||
{
|
||||
Self::new("Warning", message, source_location, sources)
|
||||
}
|
||||
|
||||
/// Returns the `<address payable>`'s `send` and `transfer` methods usage error.
|
||||
pub fn message_send_and_transfer(src: Option<&str>) -> Self {
|
||||
pub fn warning_send_and_transfer(
|
||||
node: Option<&str>,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
) -> 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!
|
||||
@@ -65,43 +122,19 @@ and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
Self::new_warning(
|
||||
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(),
|
||||
}
|
||||
node.and_then(|node| SourceLocation::try_from_ast(node, id_paths)),
|
||||
Some(sources),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the `origin` instruction usage warning.
|
||||
pub fn message_tx_origin(src: Option<&str>) -> Self {
|
||||
pub fn warning_tx_origin(
|
||||
node: Option<&str>,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
) -> 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
|
||||
@@ -110,15 +143,28 @@ to rely on tx.origin, but use msg.sender instead.
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
Self {
|
||||
component: "general".to_owned(),
|
||||
error_code: None,
|
||||
formatted_message: message.clone(),
|
||||
Self::new_warning(
|
||||
message,
|
||||
severity: "warning".to_owned(),
|
||||
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
|
||||
r#type: "Warning".to_owned(),
|
||||
}
|
||||
node.and_then(|node| SourceLocation::try_from_ast(node, id_paths)),
|
||||
Some(sources),
|
||||
)
|
||||
}
|
||||
/// Returns the `runtimeCode` code usage error.
|
||||
pub fn error_runtime_code(
|
||||
node: Option<&str>,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
) -> Self {
|
||||
let message = r#"
|
||||
Deploy and runtime code are merged in PVM, accessing `type(T).runtimeCode` is not possible.
|
||||
Please consider changing the functionality relying on reading runtime code to a different approach.
|
||||
"#;
|
||||
|
||||
Self::new_error(
|
||||
message,
|
||||
node.and_then(|node| SourceLocation::try_from_ast(node, id_paths)),
|
||||
Some(sources),
|
||||
)
|
||||
}
|
||||
|
||||
/// Appends the contract path to the message..
|
||||
@@ -126,6 +172,16 @@ to rely on tx.origin, but use msg.sender instead.
|
||||
self.formatted_message
|
||||
.push_str(format!("\n--> {path}\n").as_str());
|
||||
}
|
||||
|
||||
/// Returns true if this is an error.
|
||||
pub fn is_error(&self) -> bool {
|
||||
self.severity == "error"
|
||||
}
|
||||
|
||||
/// Returns true if this is a warning.
|
||||
pub fn is_warning(&self) -> bool {
|
||||
self.severity == "warning"
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! The `solc --standard-json` output error source location.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -17,11 +17,27 @@ pub struct SourceLocation {
|
||||
pub end: isize,
|
||||
}
|
||||
|
||||
impl FromStr for SourceLocation {
|
||||
type Err = anyhow::Error;
|
||||
impl SourceLocation {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(file: String) -> Self {
|
||||
Self {
|
||||
file,
|
||||
start: -1,
|
||||
end: -1,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
let mut parts = string.split(':');
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
/// Please note that `start` and `end` are not line and column,
|
||||
/// but absolute char offsets in the source code file.
|
||||
pub fn new_with_offsets(file: String, start: isize, end: isize) -> Self {
|
||||
Self { file, start, end }
|
||||
}
|
||||
|
||||
/// A shortcut constructor from a `solc` AST node.
|
||||
pub fn try_from_ast(source: &str, id_paths: &BTreeMap<usize, &String>) -> Option<Self> {
|
||||
let mut parts = source.split(':');
|
||||
let start = parts
|
||||
.next()
|
||||
.map(|string| string.parse::<isize>())
|
||||
@@ -32,12 +48,15 @@ impl FromStr for SourceLocation {
|
||||
.map(|string| string.parse::<isize>())
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or_default();
|
||||
let file = parts.next().unwrap_or_default().to_owned();
|
||||
let path = parts
|
||||
.next()
|
||||
.and_then(|string| string.parse::<usize>().ok())
|
||||
.and_then(|file_id| id_paths.get(&file_id))?;
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
Some(Self::new_with_offsets(
|
||||
(*path).to_owned(),
|
||||
start,
|
||||
end: start + length,
|
||||
})
|
||||
start + length,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,40 @@
|
||||
//! 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 crate::standard_json::input::settings::warning::Warning;
|
||||
use crate::standard_json::output::error::error_handler::ErrorHandler;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::SolcStandardJsonInputSettingsSelection;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::SolcStandardJsonInputSource;
|
||||
#[cfg(feature = "parallel")]
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
|
||||
use self::contract::Contract;
|
||||
use self::error::Error as SolcStandardJsonOutputError;
|
||||
use self::source::Source;
|
||||
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
pub mod 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>>>,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub contracts: BTreeMap<String, BTreeMap<String, Contract>>,
|
||||
/// The source code mapping data.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sources: Option<BTreeMap<String, Source>>,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub sources: BTreeMap<String, Source>,
|
||||
/// The compilation errors and warnings.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub errors: Option<Vec<SolcStandardJsonOutputError>>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub errors: Vec<SolcStandardJsonOutputError>,
|
||||
/// The `solc` compiler version.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
@@ -39,33 +46,156 @@ pub struct Output {
|
||||
pub revive_version: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
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(()),
|
||||
};
|
||||
/// Initializes a standard JSON output.
|
||||
///
|
||||
/// Is used for projects compiled without `solc`.
|
||||
pub fn new(
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
messages: &mut Vec<SolcStandardJsonOutputError>,
|
||||
) -> Self {
|
||||
let sources = sources
|
||||
.keys()
|
||||
.enumerate()
|
||||
.map(|(index, path)| (path.to_owned(), Source::new(index)))
|
||||
.collect::<BTreeMap<String, Source>>();
|
||||
|
||||
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 {
|
||||
contracts: BTreeMap::new(),
|
||||
sources,
|
||||
errors: std::mem::take(messages),
|
||||
|
||||
version: None,
|
||||
long_version: None,
|
||||
revive_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes a standard JSON output with messages.
|
||||
///
|
||||
/// Is used to emit errors in standard JSON mode.
|
||||
pub fn new_with_messages(messages: Vec<SolcStandardJsonOutputError>) -> Self {
|
||||
Self {
|
||||
contracts: BTreeMap::new(),
|
||||
sources: BTreeMap::new(),
|
||||
errors: messages,
|
||||
|
||||
version: None,
|
||||
long_version: None,
|
||||
revive_version: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Prunes the output JSON and prints it to stdout.
|
||||
pub fn write_and_exit(
|
||||
mut self,
|
||||
selection_to_prune: SolcStandardJsonInputSettingsSelection,
|
||||
) -> ! {
|
||||
let sources = self.sources.values_mut().collect::<Vec<&mut Source>>();
|
||||
for source in sources.into_iter() {
|
||||
if selection_to_prune
|
||||
.contains(&crate::SolcStandardJsonInputSettingsSelectionFileFlag::AST)
|
||||
{
|
||||
source.ast = None;
|
||||
}
|
||||
}
|
||||
self.errors = match self.errors.take() {
|
||||
Some(mut errors) => {
|
||||
errors.extend(messages);
|
||||
Some(errors)
|
||||
|
||||
let contracts = self
|
||||
.contracts
|
||||
.values_mut()
|
||||
.flat_map(|contracts| contracts.values_mut())
|
||||
.collect::<Vec<&mut Contract>>();
|
||||
for contract in contracts.into_iter() {
|
||||
if selection_to_prune
|
||||
.contains(&crate::SolcStandardJsonInputSettingsSelectionFileFlag::Metadata)
|
||||
{
|
||||
contract.metadata = serde_json::Value::Null;
|
||||
}
|
||||
None => Some(messages),
|
||||
};
|
||||
if selection_to_prune
|
||||
.contains(&crate::SolcStandardJsonInputSettingsSelectionFileFlag::Yul)
|
||||
{
|
||||
contract.ir_optimized = String::new();
|
||||
}
|
||||
if let Some(ref mut evm) = contract.evm {
|
||||
if selection_to_prune.contains(
|
||||
&crate::SolcStandardJsonInputSettingsSelectionFileFlag::MethodIdentifiers,
|
||||
) {
|
||||
evm.method_identifiers.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.contracts.retain(|_, contracts| {
|
||||
contracts.retain(|_, contract| !contract.is_empty());
|
||||
!contracts.is_empty()
|
||||
});
|
||||
|
||||
serde_json::to_writer(std::io::stdout(), &self).expect("Stdout writing error");
|
||||
std::process::exit(revive_common::EXIT_CODE_SUCCESS);
|
||||
}
|
||||
|
||||
/// Traverses the AST and returns the list of additional errors and warnings.
|
||||
pub fn preprocess_ast(
|
||||
&mut self,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
suppressed_warnings: &[Warning],
|
||||
) -> anyhow::Result<()> {
|
||||
let id_paths: BTreeMap<usize, &String> = self
|
||||
.sources
|
||||
.iter()
|
||||
.map(|(path, source)| (source.id, path))
|
||||
.collect();
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
let iter = self.sources.par_iter();
|
||||
#[cfg(not(feature = "parallel"))]
|
||||
let iter = self.sources.iter();
|
||||
|
||||
let messages: Vec<SolcStandardJsonOutputError> = iter
|
||||
.flat_map(|(_path, source)| {
|
||||
source
|
||||
.ast
|
||||
.as_ref()
|
||||
.map(|ast| Source::get_messages(ast, &id_paths, sources, suppressed_warnings))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect();
|
||||
self.errors.extend(messages);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pushes an arbitrary error with path.
|
||||
///
|
||||
/// Please do not push project-general errors without paths here.
|
||||
pub fn push_error(&mut self, path: Option<String>, error: anyhow::Error) {
|
||||
use crate::standard_json::output::error::source_location::SourceLocation;
|
||||
|
||||
self.errors.push(SolcStandardJsonOutputError::new_error(
|
||||
error,
|
||||
path.map(SourceLocation::new),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorHandler for Output {
|
||||
fn errors(&self) -> Vec<&SolcStandardJsonOutputError> {
|
||||
self.errors
|
||||
.iter()
|
||||
.filter(|error| error.is_error())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn take_warnings(&mut self) -> Vec<SolcStandardJsonOutputError> {
|
||||
let warnings = self
|
||||
.errors
|
||||
.iter()
|
||||
.filter(|message| message.is_warning())
|
||||
.cloned()
|
||||
.collect();
|
||||
self.errors.retain(|message| !message.is_warning());
|
||||
warnings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
//! The `solc --standard-json` output source.
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::standard_json::input::settings::warning::Warning;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::standard_json::output::error::Error as SolcStandardJsonOutputError;
|
||||
#[cfg(feature = "resolc")]
|
||||
use crate::warning::Warning;
|
||||
use crate::SolcStandardJsonInputSource;
|
||||
|
||||
/// The `solc --standard-json` output source.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -17,160 +23,126 @@ pub struct Source {
|
||||
pub ast: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "resolc")]
|
||||
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(),
|
||||
))
|
||||
/// Initializes a standard JSON source.
|
||||
///
|
||||
/// Is used for projects compiled without `solc`.
|
||||
pub fn new(id: usize) -> Self {
|
||||
Self { id, ast: None }
|
||||
}
|
||||
|
||||
/// 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(
|
||||
/// Checks the AST node for the usage of send or transfer address methods.
|
||||
pub fn check_send_and_transfer(
|
||||
ast: &serde_json::Value,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
) -> 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;
|
||||
}
|
||||
(ast.get("nodeType")?.as_str()? == "FunctionCall").then_some(())?;
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_extcodesize(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
(expression.get("nodeType")?.as_str()? == "MemberAccess").then_some(())?;
|
||||
let member_name = expression.get("memberName")?.as_str()?;
|
||||
["send", "transfer"].contains(&member_name).then_some(())?;
|
||||
|
||||
let expression = expression.get("expression")?.as_object()?;
|
||||
let type_descriptions = expression.get("typeDescriptions")?.as_object()?;
|
||||
let type_identifier = type_descriptions.get("typeIdentifier")?.as_str()?;
|
||||
["t_address_payable"]
|
||||
.contains(&type_identifier)
|
||||
.then_some(())?;
|
||||
|
||||
Some(Warning::SendAndTransfer.as_error(ast.get("src")?.as_str(), id_paths, sources))
|
||||
}
|
||||
|
||||
/// Checks the AST node for the `origin` assembly instruction usage.
|
||||
pub fn check_assembly_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
/// Checks the AST node for the usage of runtime code.
|
||||
pub fn check_runtime_code(
|
||||
ast: &serde_json::Value,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
) -> 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;
|
||||
}
|
||||
(ast.get("nodeType")?.as_str()? == "MemberAccess").then_some(())?;
|
||||
(ast.get("memberName")?.as_str()? == "runtimeCode").then_some(())?;
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_tx_origin(
|
||||
let expression = ast.get("expression")?.as_object()?;
|
||||
let type_descriptions = expression.get("typeDescriptions")?.as_object()?;
|
||||
type_descriptions
|
||||
.get("typeIdentifier")?
|
||||
.as_str()?
|
||||
.starts_with("t_magic_meta_type")
|
||||
.then_some(())?;
|
||||
|
||||
Some(SolcStandardJsonOutputError::error_runtime_code(
|
||||
ast.get("src")?.as_str(),
|
||||
id_paths,
|
||||
sources,
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks the AST node for the `tx.origin` value usage.
|
||||
pub fn check_tx_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
|
||||
pub fn check_tx_origin(
|
||||
ast: &serde_json::Value,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
) -> 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;
|
||||
}
|
||||
(ast.get("nodeType")?.as_str()? == "MemberAccess").then_some(())?;
|
||||
(ast.get("memberName")?.as_str()? == "origin").then_some(())?;
|
||||
|
||||
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;
|
||||
}
|
||||
(expression.get("nodeType")?.as_str()? == "Identifier").then_some(())?;
|
||||
(expression.get("name")?.as_str()? == "tx").then_some(())?;
|
||||
|
||||
Some(SolcStandardJsonOutputError::message_tx_origin(
|
||||
ast.get("src")?.as_str(),
|
||||
))
|
||||
Some(Warning::TxOrigin.as_error(ast.get("src")?.as_str(), id_paths, sources))
|
||||
}
|
||||
|
||||
/// Returns the list of messages for some specific parts of the AST.
|
||||
#[cfg(feature = "resolc")]
|
||||
pub fn get_messages(
|
||||
ast: &serde_json::Value,
|
||||
id_paths: &BTreeMap<usize, &String>,
|
||||
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
|
||||
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) {
|
||||
if !suppressed_warnings.contains(&Warning::SendAndTransfer) {
|
||||
if let Some(message) = Self::check_send_and_transfer(ast, id_paths, sources) {
|
||||
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) {
|
||||
if let Some(message) = Self::check_tx_origin(ast, id_paths, sources) {
|
||||
messages.push(message);
|
||||
}
|
||||
}
|
||||
if let Some(message) = Self::check_runtime_code(ast, id_paths, sources) {
|
||||
messages.push(message);
|
||||
}
|
||||
|
||||
match ast {
|
||||
serde_json::Value::Array(array) => {
|
||||
for element in array.iter() {
|
||||
messages.extend(Self::get_messages(element, suppressed_warnings));
|
||||
messages.extend(Self::get_messages(
|
||||
element,
|
||||
id_paths,
|
||||
sources,
|
||||
suppressed_warnings,
|
||||
));
|
||||
}
|
||||
}
|
||||
serde_json::Value::Object(object) => {
|
||||
for (_key, value) in object.iter() {
|
||||
messages.extend(Self::get_messages(value, suppressed_warnings));
|
||||
messages.extend(Self::get_messages(
|
||||
value,
|
||||
id_paths,
|
||||
sources,
|
||||
suppressed_warnings,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -178,25 +150,4 @@ impl Source {
|
||||
|
||||
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,
|
||||
},
|
||||
)
|
||||
.next_back()
|
||||
.ok_or_else(|| anyhow::anyhow!("The last contract not found in the AST"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
//! `resolc` custom compiler warnings.
|
||||
//!
|
||||
//! The revive compiler adds warnings only applicable when compilng
|
||||
//! to the revive stack on Polkadot to the output.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
// The `resolc` custom compiler warning.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Warning {
|
||||
EcRecover,
|
||||
SendTransfer,
|
||||
ExtCodeSize,
|
||||
TxOrigin,
|
||||
BlockTimestamp,
|
||||
BlockNumber,
|
||||
BlockHash,
|
||||
}
|
||||
|
||||
impl Warning {
|
||||
/// Converts string arguments into an array of warnings.
|
||||
pub fn try_from_strings(strings: &[String]) -> Result<Vec<Self>, anyhow::Error> {
|
||||
strings
|
||||
.iter()
|
||||
.map(|string| Self::from_str(string))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Warning {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"ecrecover" => Ok(Self::EcRecover),
|
||||
"sendtransfer" => Ok(Self::SendTransfer),
|
||||
"extcodesize" => Ok(Self::ExtCodeSize),
|
||||
"txorigin" => Ok(Self::TxOrigin),
|
||||
"blocktimestamp" => Ok(Self::BlockTimestamp),
|
||||
"blocknumber" => Ok(Self::BlockNumber),
|
||||
"blockhash" => Ok(Self::BlockHash),
|
||||
_ => Err(anyhow::anyhow!("Invalid warning: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user