foundry_config/
error.rs

1//! error handling and solc error codes
2use alloy_primitives::map::HashSet;
3use figment::providers::{Format, Toml};
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::{error::Error, fmt, str::FromStr};
6
7/// Represents a failed attempt to extract `Config` from a `Figment`
8#[derive(Clone, PartialEq)]
9pub struct ExtractConfigError {
10    /// error thrown when extracting the `Config`
11    pub(crate) error: figment::Error,
12}
13
14impl ExtractConfigError {
15    /// Wraps the figment error.
16    pub fn new(error: figment::Error) -> Self {
17        Self { error }
18    }
19}
20
21impl fmt::Display for ExtractConfigError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        let mut unique_errors = Vec::with_capacity(self.error.count());
24        let mut unique = HashSet::with_capacity(self.error.count());
25        for err in self.error.clone().into_iter() {
26            let err = if err
27                .metadata
28                .as_ref()
29                .map(|meta| meta.name.contains(Toml::NAME))
30                .unwrap_or_default()
31            {
32                FoundryConfigError::Toml(err)
33            } else {
34                FoundryConfigError::Other(err)
35            };
36
37            if unique.insert(err.to_string()) {
38                unique_errors.push(err);
39            }
40        }
41        writeln!(f, "failed to extract foundry config:")?;
42        for err in unique_errors {
43            writeln!(f, "{err}")?;
44        }
45        Ok(())
46    }
47}
48
49impl fmt::Debug for ExtractConfigError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        fmt::Display::fmt(self, f)
52    }
53}
54
55impl Error for ExtractConfigError {
56    fn source(&self) -> Option<&(dyn Error + 'static)> {
57        Error::source(&self.error)
58    }
59}
60
61/// Represents an error that can occur when constructing the `Config`
62#[derive(Clone, Debug, PartialEq)]
63pub enum FoundryConfigError {
64    /// An error thrown during toml parsing
65    Toml(figment::Error),
66    /// Any other error thrown when constructing the config's figment.
67    Other(figment::Error),
68}
69
70impl fmt::Display for FoundryConfigError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        let fmt_err = |err: &figment::Error, f: &mut fmt::Formatter<'_>| {
73            write!(f, "{err}")?;
74            if !err.path.is_empty() {
75                // the path will contain the setting value like `["etherscan_api_key"]`
76                write!(f, " for setting `{}`", err.path.join("."))?;
77            }
78            Ok(())
79        };
80
81        match self {
82            Self::Toml(err) => {
83                f.write_str("foundry.toml error: ")?;
84                fmt_err(err, f)
85            }
86            Self::Other(err) => {
87                f.write_str("foundry config error: ")?;
88                fmt_err(err, f)
89            }
90        }
91    }
92}
93
94impl Error for FoundryConfigError {
95    fn source(&self) -> Option<&(dyn Error + 'static)> {
96        match self {
97            Self::Other(error) | Self::Toml(error) => Error::source(error),
98        }
99    }
100}
101
102/// A non-exhaustive list of solidity error codes
103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub enum SolidityErrorCode {
105    /// Warning that SPDX license identifier not provided in source file
106    SpdxLicenseNotProvided,
107    /// Warning: Visibility for constructor is ignored. If you want the contract to be
108    /// non-deployable, making it "abstract" is sufficient
109    VisibilityForConstructorIsIgnored,
110    /// Warning that contract code size exceeds 24576 bytes (a limit introduced in Spurious
111    /// Dragon).
112    ContractExceeds24576Bytes,
113    /// Warning after shanghai if init code size exceeds 49152 bytes
114    ContractInitCodeSizeExceeds49152Bytes,
115    /// Warning that Function state mutability can be restricted to view/pure.
116    FunctionStateMutabilityCanBeRestricted,
117    /// Warning: Unused local variable
118    UnusedLocalVariable,
119    /// Warning: Unused function parameter. Remove or comment out the variable name to silence this
120    /// warning.
121    UnusedFunctionParameter,
122    /// Warning: Return value of low-level calls not used.
123    ReturnValueOfCallsNotUsed,
124    ///  Warning: Interface functions are implicitly "virtual"
125    InterfacesExplicitlyVirtual,
126    /// Warning: This contract has a payable fallback function, but no receive ether function.
127    /// Consider adding a receive ether function.
128    PayableNoReceiveEther,
129    ///  Warning: This declaration shadows an existing declaration.
130    ShadowsExistingDeclaration,
131    /// This declaration has the same name as another declaration.
132    DeclarationSameNameAsAnother,
133    /// Unnamed return variable can remain unassigned
134    UnnamedReturnVariable,
135    /// Unreachable code
136    Unreachable,
137    /// Missing pragma solidity
138    PragmaSolidity,
139    /// Uses transient opcodes
140    TransientStorageUsed,
141    /// There are more than 256 warnings. Ignoring the rest.
142    TooManyWarnings,
143    /// Warning: 'transfer' is deprecated and scheduled for removal.
144    TransferDeprecated,
145    /// Warning: Natspec memory-safe-assembly special comment for inline assembly is deprecated.
146    NatspecMemorySafeAssemblyDeprecated,
147    /// All other error codes
148    Other(u64),
149}
150
151impl SolidityErrorCode {
152    /// The textual identifier for this error
153    ///
154    /// Returns `Err(code)` if unknown error
155    pub fn as_str(&self) -> Result<&'static str, u64> {
156        let s = match self {
157            Self::SpdxLicenseNotProvided => "license",
158            Self::VisibilityForConstructorIsIgnored => "constructor-visibility",
159            Self::ContractExceeds24576Bytes => "code-size",
160            Self::ContractInitCodeSizeExceeds49152Bytes => "init-code-size",
161            Self::FunctionStateMutabilityCanBeRestricted => "func-mutability",
162            Self::UnusedLocalVariable => "unused-var",
163            Self::UnusedFunctionParameter => "unused-param",
164            Self::ReturnValueOfCallsNotUsed => "unused-return",
165            Self::InterfacesExplicitlyVirtual => "virtual-interfaces",
166            Self::PayableNoReceiveEther => "missing-receive-ether",
167            Self::ShadowsExistingDeclaration => "shadowing",
168            Self::DeclarationSameNameAsAnother => "same-varname",
169            Self::UnnamedReturnVariable => "unnamed-return",
170            Self::Unreachable => "unreachable",
171            Self::PragmaSolidity => "pragma-solidity",
172            Self::TransientStorageUsed => "transient-storage",
173            Self::TooManyWarnings => "too-many-warnings",
174            Self::TransferDeprecated => "transfer-deprecated",
175            Self::NatspecMemorySafeAssemblyDeprecated => "natspec-memory-safe-assembly-deprecated",
176            Self::Other(code) => return Err(*code),
177        };
178        Ok(s)
179    }
180}
181
182impl From<SolidityErrorCode> for u64 {
183    fn from(code: SolidityErrorCode) -> Self {
184        match code {
185            SolidityErrorCode::SpdxLicenseNotProvided => 1878,
186            SolidityErrorCode::VisibilityForConstructorIsIgnored => 2462,
187            SolidityErrorCode::ContractExceeds24576Bytes => 5574,
188            SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860,
189            SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => 2018,
190            SolidityErrorCode::UnusedLocalVariable => 2072,
191            SolidityErrorCode::UnusedFunctionParameter => 5667,
192            SolidityErrorCode::ReturnValueOfCallsNotUsed => 9302,
193            SolidityErrorCode::InterfacesExplicitlyVirtual => 5815,
194            SolidityErrorCode::PayableNoReceiveEther => 3628,
195            SolidityErrorCode::ShadowsExistingDeclaration => 2519,
196            SolidityErrorCode::DeclarationSameNameAsAnother => 8760,
197            SolidityErrorCode::UnnamedReturnVariable => 6321,
198            SolidityErrorCode::Unreachable => 5740,
199            SolidityErrorCode::PragmaSolidity => 3420,
200            SolidityErrorCode::TransientStorageUsed => 2394,
201            SolidityErrorCode::TooManyWarnings => 4591,
202            SolidityErrorCode::TransferDeprecated => 9207,
203            SolidityErrorCode::NatspecMemorySafeAssemblyDeprecated => 2424,
204            SolidityErrorCode::Other(code) => code,
205        }
206    }
207}
208
209impl fmt::Display for SolidityErrorCode {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        match self.as_str() {
212            Ok(name) => name.fmt(f),
213            Err(code) => code.fmt(f),
214        }
215    }
216}
217
218impl FromStr for SolidityErrorCode {
219    type Err = String;
220
221    fn from_str(s: &str) -> Result<Self, Self::Err> {
222        let code = match s {
223            "license" => Self::SpdxLicenseNotProvided,
224            "constructor-visibility" => Self::VisibilityForConstructorIsIgnored,
225            "code-size" => Self::ContractExceeds24576Bytes,
226            "init-code-size" => Self::ContractInitCodeSizeExceeds49152Bytes,
227            "func-mutability" => Self::FunctionStateMutabilityCanBeRestricted,
228            "unused-var" => Self::UnusedLocalVariable,
229            "unused-param" => Self::UnusedFunctionParameter,
230            "unused-return" => Self::ReturnValueOfCallsNotUsed,
231            "virtual-interfaces" => Self::InterfacesExplicitlyVirtual,
232            "missing-receive-ether" => Self::PayableNoReceiveEther,
233            "shadowing" => Self::ShadowsExistingDeclaration,
234            "same-varname" => Self::DeclarationSameNameAsAnother,
235            "unnamed-return" => Self::UnnamedReturnVariable,
236            "unreachable" => Self::Unreachable,
237            "pragma-solidity" => Self::PragmaSolidity,
238            "transient-storage" => Self::TransientStorageUsed,
239            "too-many-warnings" => Self::TooManyWarnings,
240            "transfer-deprecated" => Self::TransferDeprecated,
241            "natspec-memory-safe-assembly-deprecated" => Self::NatspecMemorySafeAssemblyDeprecated,
242            _ => return Err(format!("Unknown variant {s}")),
243        };
244
245        Ok(code)
246    }
247}
248
249impl From<u64> for SolidityErrorCode {
250    fn from(code: u64) -> Self {
251        match code {
252            1878 => Self::SpdxLicenseNotProvided,
253            2462 => Self::VisibilityForConstructorIsIgnored,
254            5574 => Self::ContractExceeds24576Bytes,
255            3860 => Self::ContractInitCodeSizeExceeds49152Bytes,
256            2018 => Self::FunctionStateMutabilityCanBeRestricted,
257            2072 => Self::UnusedLocalVariable,
258            5667 => Self::UnusedFunctionParameter,
259            9302 => Self::ReturnValueOfCallsNotUsed,
260            5815 => Self::InterfacesExplicitlyVirtual,
261            3628 => Self::PayableNoReceiveEther,
262            2519 => Self::ShadowsExistingDeclaration,
263            8760 => Self::DeclarationSameNameAsAnother,
264            6321 => Self::UnnamedReturnVariable,
265            5740 => Self::Unreachable,
266            3420 => Self::PragmaSolidity,
267            2394 => Self::TransientStorageUsed,
268            4591 => Self::TooManyWarnings,
269            9207 => Self::TransferDeprecated,
270            2424 => Self::NatspecMemorySafeAssemblyDeprecated,
271            other => Self::Other(other),
272        }
273    }
274}
275
276impl Serialize for SolidityErrorCode {
277    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
278    where
279        S: Serializer,
280    {
281        match self.as_str() {
282            Ok(alias) => serializer.serialize_str(alias),
283            Err(code) => serializer.serialize_u64(code),
284        }
285    }
286}
287
288impl<'de> Deserialize<'de> for SolidityErrorCode {
289    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
290    where
291        D: Deserializer<'de>,
292    {
293        /// Helper deserializer for error codes as names and codes
294        #[derive(Deserialize)]
295        #[serde(untagged)]
296        enum SolCode {
297            Name(String),
298            Code(u64),
299        }
300
301        match SolCode::deserialize(deserializer)? {
302            SolCode::Code(code) => Ok(code.into()),
303            SolCode::Name(name) => name.parse().map_err(serde::de::Error::custom),
304        }
305    }
306}