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;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Error {
pub component: String,
pub error_code: Option<String>,
pub formatted_message: String,
pub message: String,
pub severity: String,
pub source_location: Option<SourceLocation>,
pub r#type: String,
}
impl Error {
pub const IGNORED_WARNING_CODES: [&'static str; 5] = ["1699", "3860", "5159", "5574", "6417"];
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,
severity: r#type.to_lowercase(),
source_location,
r#type: r#type.to_owned(),
}
}
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)
}
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)
}
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!
The revive runtime uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls and
the gas stipend used by the runtime is different from the EVM.
You are advised to carefully test this, employ re-entrancy guards or use the withdrawal pattern instead!
Learn more on https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy
and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts
"#
.to_owned();
Self::new_warning(
message,
node.and_then(|node| SourceLocation::try_from_ast(node, id_paths)),
Some(sources),
)
}
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
transaction might be different from the contract calling your code. It is highly recommended NOT
to rely on tx.origin, but use msg.sender instead.
"#
.to_owned();
Self::new_warning(
message,
node.and_then(|node| SourceLocation::try_from_ast(node, id_paths)),
Some(sources),
)
}
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),
)
}
pub fn push_contract_path(&mut self, path: &str) {
self.formatted_message
.push_str(format!("\n--> {path}\n").as_str());
}
pub fn is_error(&self) -> bool {
self.severity == "error"
}
pub fn is_warning(&self) -> bool {
self.severity == "warning"
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.formatted_message)
}
}