1use figment::providers::{Format, Toml};
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use std::{collections::HashSet, error::Error, fmt, str::FromStr};
5
6pub const FAILED_TO_EXTRACT_CONFIG_PANIC_MSG: &str = "failed to extract foundry config:";
8
9#[derive(Clone, Debug, PartialEq)]
11pub struct ExtractConfigError {
12 pub(crate) error: figment::Error,
14}
15
16impl ExtractConfigError {
17 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#[derive(Clone, Debug, PartialEq)]
59pub enum FoundryConfigError {
60 Toml(figment::Error),
62 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub enum SolidityErrorCode {
101 SpdxLicenseNotProvided,
103 VisibilityForConstructorIsIgnored,
106 ContractExceeds24576Bytes,
109 ContractInitCodeSizeExceeds49152Bytes,
111 FunctionStateMutabilityCanBeRestricted,
113 UnusedLocalVariable,
115 UnusedFunctionParameter,
118 ReturnValueOfCallsNotUsed,
120 InterfacesExplicitlyVirtual,
122 PayableNoReceiveEther,
125 ShadowsExistingDeclaration,
127 DeclarationSameNameAsAnother,
129 UnnamedReturnVariable,
131 Unreachable,
133 PragmaSolidity,
135 TransientStorageUsed,
137 TooManyWarnings,
139 Other(u64),
141}
142
143impl SolidityErrorCode {
144 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 #[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}