1use crate::DecimalFloat;
2use alloy::hex::FromHexError;
3use alloy::primitives::{Bytes, FixedBytes};
4use alloy::sol_types::SolError;
5use revm::context::result::{EVMError, HaltReason, Output, SuccessReason};
6use std::thread::AccessError;
7use thiserror::Error;
8use wasm_bindgen_utils::prelude::js_sys::{Error as JsError, RangeError};
9use wasm_bindgen_utils::result::WasmEncodedError;
10
11#[derive(Debug, Error)]
12pub enum FloatError {
13 #[error("EVM error: {0}")]
14 Evm(#[from] EVMError<std::convert::Infallible>),
15 #[error("Float execution reverted with output: {0}")]
16 Revert(Bytes),
17 #[error("Float execution halted with reason: {0:?}")]
18 Halt(HaltReason),
19 #[error("Execution ended for non-return reason. Reason: {0:?}. Output: {1:?}")]
20 UnexpectedSuccess(SuccessReason, Output),
21 #[error(transparent)]
22 AlloySolTypes(#[from] alloy::sol_types::Error),
23 #[error("Decimal Float error: {0:?}")]
24 DecimalFloat(Box<DecimalFloat::DecimalFloatErrors>),
25 #[error("Decimal Float error selector: {0:?}")]
26 DecimalFloatSelector(Result<DecimalFloatErrorSelector, FixedBytes<4>>),
27 #[error(transparent)]
28 Access(#[from] AccessError),
29 #[error("Invalid hex string: {0}")]
30 InvalidHex(String),
31 #[error(transparent)]
32 AlloyFromHexError(#[from] FromHexError),
33 #[error(transparent)]
34 AlloyParseError(#[from] alloy::primitives::ruint::ParseError),
35 #[error(transparent)]
36 AlloyParseSignedError(#[from] alloy::primitives::ParseSignedError),
37 #[error("Wasm bindgen js_sys threw error: {0}")]
38 JsSysError(String),
39}
40
41#[derive(Debug)]
42pub enum DecimalFloatErrorSelector {
43 CoefficientOverflow,
44 ExponentOverflow,
45 ExponentUnderflow,
46 FixedDecimalOverflow,
47 Log10Negative,
48 Log10Zero,
49 LossyConversionFromFloat,
50 NegativeFixedDecimalConversion,
51 WithTargetExponentOverflow,
52}
53
54impl DecimalFloatErrorSelector {
55 pub fn to_readable_msg(&self) -> &'static str {
58 match self {
59 Self::CoefficientOverflow => {
60 "The number's coefficient is too large to fit in a Float (the signed coefficient exceeds the 224-bit range)."
61 }
62 Self::ExponentOverflow => {
63 "The number is too large to represent as a Float (its exponent exceeds the maximum supported magnitude)."
64 }
65 Self::ExponentUnderflow => {
66 "The number is too small to represent as a Float (its exponent is below the minimum supported magnitude, so it cannot be distinguished from zero)."
67 }
68 Self::FixedDecimalOverflow => {
69 "The number is too large to convert to a fixed-decimal value at the requested number of decimals (the scaled value exceeds the unsigned 256-bit range)."
70 }
71 Self::Log10Negative => {
72 "Cannot take the base-10 logarithm of a negative number."
73 }
74 Self::Log10Zero => "Cannot take the base-10 logarithm of zero.",
75 Self::LossyConversionFromFloat => {
76 "Converting this Float to the requested type would lose precision, and a lossless conversion was required."
77 }
78 Self::NegativeFixedDecimalConversion => {
79 "Cannot convert a negative number to an unsigned fixed-decimal value."
80 }
81 Self::WithTargetExponentOverflow => {
82 "The number cannot be rescaled to the requested target exponent without overflowing the Float coefficient."
83 }
84 }
85 }
86}
87
88impl TryFrom<FixedBytes<4>> for DecimalFloatErrorSelector {
89 type Error = FixedBytes<4>;
90
91 fn try_from(error_selector: FixedBytes<4>) -> Result<Self, Self::Error> {
92 let FixedBytes(bytes) = error_selector;
93 match bytes {
94 <DecimalFloat::CoefficientOverflow as SolError>::SELECTOR => {
95 Ok(Self::CoefficientOverflow)
96 }
97 <DecimalFloat::ExponentOverflow as SolError>::SELECTOR => Ok(Self::ExponentOverflow),
98 <DecimalFloat::ExponentUnderflow as SolError>::SELECTOR => Ok(Self::ExponentUnderflow),
99 <DecimalFloat::FixedDecimalOverflow as SolError>::SELECTOR => {
100 Ok(Self::FixedDecimalOverflow)
101 }
102 <DecimalFloat::Log10Negative as SolError>::SELECTOR => Ok(Self::Log10Negative),
103 <DecimalFloat::Log10Zero as SolError>::SELECTOR => Ok(Self::Log10Zero),
104 <DecimalFloat::LossyConversionFromFloat as SolError>::SELECTOR => {
105 Ok(Self::LossyConversionFromFloat)
106 }
107 <DecimalFloat::NegativeFixedDecimalConversion as SolError>::SELECTOR => {
108 Ok(Self::NegativeFixedDecimalConversion)
109 }
110 <DecimalFloat::WithTargetExponentOverflow as SolError>::SELECTOR => {
111 Ok(Self::WithTargetExponentOverflow)
112 }
113 _ => Err(error_selector),
114 }
115 }
116}
117
118impl FloatError {
119 pub fn to_readable_msg(&self) -> String {
122 match self {
123 Self::Evm(e) => {
124 format!("An error occurred while executing the Float operation in the EVM: {e}")
125 }
126 Self::Revert(bytes) => {
127 format!("The Float operation reverted with output: {bytes}")
128 }
129 Self::Halt(reason) => {
130 format!("The Float operation halted unexpectedly with reason: {reason:?}")
131 }
132 Self::UnexpectedSuccess(reason, output) => {
133 format!(
134 "The Float operation ended for an unexpected non-return reason: {reason:?}. Output: {output:?}"
135 )
136 }
137 Self::AlloySolTypes(e) => {
138 format!("Failed to encode or decode the Float operation's ABI data: {e}")
139 }
140 Self::DecimalFloat(e) => {
141 format!("The Float operation failed with a decimal float error: {e:?}")
142 }
143 Self::DecimalFloatSelector(selector) => match selector {
144 Ok(selector) => selector.to_readable_msg().to_string(),
145 Err(unknown) => format!(
146 "The Float operation reverted with an unrecognised error selector: {unknown}"
147 ),
148 },
149 Self::Access(e) => {
150 format!("Failed to access the thread-local EVM used to run Float operations: {e}")
151 }
152 Self::InvalidHex(s) => {
153 format!("The provided value is not a valid hex string: {s}")
154 }
155 Self::AlloyFromHexError(e) => {
156 format!("Failed to decode the provided hex string: {e}")
157 }
158 Self::AlloyParseError(e) => {
159 format!("Failed to parse the provided number: {e}")
160 }
161 Self::AlloyParseSignedError(e) => {
162 format!("Failed to parse the provided signed number: {e}")
163 }
164 Self::JsSysError(s) => {
165 format!("A JavaScript error occurred while running the Float operation: {s}")
166 }
167 }
168 }
169}
170
171impl From<FloatError> for WasmEncodedError {
172 fn from(value: FloatError) -> Self {
173 WasmEncodedError {
174 msg: value.to_string(),
175 readable_msg: value.to_readable_msg(),
176 }
177 }
178}
179
180impl From<JsError> for FloatError {
181 fn from(value: JsError) -> Self {
182 FloatError::JsSysError(value.to_string().into())
183 }
184}
185
186impl From<RangeError> for FloatError {
187 fn from(value: RangeError) -> Self {
188 FloatError::JsSysError(value.to_string().into())
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_decimal_float_error_selector_readable_msgs() {
198 assert_eq!(
199 DecimalFloatErrorSelector::CoefficientOverflow.to_readable_msg(),
200 "The number's coefficient is too large to fit in a Float (the signed coefficient exceeds the 224-bit range)."
201 );
202 assert_eq!(
203 DecimalFloatErrorSelector::ExponentOverflow.to_readable_msg(),
204 "The number is too large to represent as a Float (its exponent exceeds the maximum supported magnitude)."
205 );
206 assert_eq!(
207 DecimalFloatErrorSelector::ExponentUnderflow.to_readable_msg(),
208 "The number is too small to represent as a Float (its exponent is below the minimum supported magnitude, so it cannot be distinguished from zero)."
209 );
210 assert_eq!(
211 DecimalFloatErrorSelector::FixedDecimalOverflow.to_readable_msg(),
212 "The number is too large to convert to a fixed-decimal value at the requested number of decimals (the scaled value exceeds the unsigned 256-bit range)."
213 );
214 assert_eq!(
215 DecimalFloatErrorSelector::Log10Negative.to_readable_msg(),
216 "Cannot take the base-10 logarithm of a negative number."
217 );
218 assert_eq!(
219 DecimalFloatErrorSelector::Log10Zero.to_readable_msg(),
220 "Cannot take the base-10 logarithm of zero."
221 );
222 assert_eq!(
223 DecimalFloatErrorSelector::LossyConversionFromFloat.to_readable_msg(),
224 "Converting this Float to the requested type would lose precision, and a lossless conversion was required."
225 );
226 assert_eq!(
227 DecimalFloatErrorSelector::NegativeFixedDecimalConversion.to_readable_msg(),
228 "Cannot convert a negative number to an unsigned fixed-decimal value."
229 );
230 assert_eq!(
231 DecimalFloatErrorSelector::WithTargetExponentOverflow.to_readable_msg(),
232 "The number cannot be rescaled to the requested target exponent without overflowing the Float coefficient."
233 );
234 }
235
236 #[test]
237 fn test_float_error_readable_msg_invalid_hex() {
238 let err = FloatError::InvalidHex("zz".to_string());
239 assert_eq!(
240 err.to_readable_msg(),
241 "The provided value is not a valid hex string: zz"
242 );
243 }
244
245 #[test]
246 fn test_float_error_readable_msg_js_sys() {
247 let err = FloatError::JsSysError("boom".to_string());
248 assert_eq!(
249 err.to_readable_msg(),
250 "A JavaScript error occurred while running the Float operation: boom"
251 );
252 }
253
254 #[test]
255 fn test_float_error_readable_msg_decimal_float_selector_known() {
256 let err = FloatError::DecimalFloatSelector(Ok(DecimalFloatErrorSelector::Log10Zero));
257 assert_eq!(
258 err.to_readable_msg(),
259 "Cannot take the base-10 logarithm of zero."
260 );
261 }
262
263 #[test]
264 fn test_float_error_readable_msg_decimal_float_selector_unknown() {
265 let unknown = FixedBytes::<4>::from([0xde, 0xad, 0xbe, 0xef]);
266 let err = FloatError::DecimalFloatSelector(Err(unknown));
267 assert_eq!(
268 err.to_readable_msg(),
269 "The Float operation reverted with an unrecognised error selector: 0xdeadbeef"
270 );
271 }
272
273 #[test]
274 fn test_wasm_encoded_error_uses_readable_msg() {
275 let err = FloatError::InvalidHex("zz".to_string());
276 let short = err.to_string();
277 let readable = err.to_readable_msg();
278 let encoded: WasmEncodedError = err.into();
279 assert_eq!(encoded.msg, short);
280 assert_eq!(encoded.readable_msg, readable);
281 assert_ne!(encoded.msg, encoded.readable_msg);
283 }
284}