cyfrin_foundry_config/
error.rs

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