ethers_abi/token/
lenient.rs

1// Copyright 2015-2020 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use crate::{
10    errors::Error,
11    token::{StrictTokenizer, Tokenizer},
12    Uint,
13};
14use std::borrow::Cow;
15
16use once_cell::sync::Lazy;
17static RE: Lazy<regex::Regex> = Lazy::new(|| {
18    regex::Regex::new(r"^([0-9]+)(\.[0-9]+)?\s*(ether|gwei|nanoether|nano|wei)$")
19        .expect("invalid regex")
20});
21
22/// Tries to parse string as a token. Does not require string to clearly represent the value.
23pub struct LenientTokenizer;
24
25impl Tokenizer for LenientTokenizer {
26    fn tokenize_address(value: &str) -> Result<[u8; 20], Error> {
27        StrictTokenizer::tokenize_address(value)
28    }
29
30    fn tokenize_string(value: &str) -> Result<String, Error> {
31        StrictTokenizer::tokenize_string(value)
32    }
33
34    fn tokenize_bool(value: &str) -> Result<bool, Error> {
35        StrictTokenizer::tokenize_bool(value)
36    }
37
38    fn tokenize_bytes(value: &str) -> Result<Vec<u8>, Error> {
39        StrictTokenizer::tokenize_bytes(value)
40    }
41
42    fn tokenize_fixed_bytes(value: &str, len: usize) -> Result<Vec<u8>, Error> {
43        StrictTokenizer::tokenize_fixed_bytes(value, len)
44    }
45
46    fn tokenize_uint(value: &str) -> Result<[u8; 32], Error> {
47        let result = StrictTokenizer::tokenize_uint(value);
48        if result.is_ok() {
49            return result;
50        }
51
52        // Tries to parse it as is first. If it fails, tries to check for
53        // expectable units with the following format: 'Number[Spaces]Unit'.
54        //   If regex fails, then the original FromDecStrErr should take priority
55        let uint = match Uint::from_dec_str(value) {
56            Ok(_uint) => _uint,
57            Err(dec_error) => {
58                let original_dec_error = dec_error.to_string();
59
60                match RE.captures(value) {
61                    Some(captures) => {
62                        let integer = captures
63                            .get(1)
64                            .expect("capture group does not exist")
65                            .as_str();
66                        let fract = captures
67                            .get(2)
68                            .map(|c| c.as_str().trim_start_matches('.'))
69                            .unwrap_or_else(|| "");
70                        let units = captures
71                            .get(3)
72                            .expect("capture group does not exist")
73                            .as_str();
74
75                        let units = Uint::from(match units.to_lowercase().as_str() {
76                            "ether" => 18,
77                            "gwei" | "nano" | "nanoether" => 9,
78                            "wei" => 0,
79                            _ => return Err(dec_error.into()),
80                        });
81
82                        let integer =
83                            Uint::from_dec_str(integer)?.checked_mul(Uint::from(10u32).pow(units));
84
85                        if fract.is_empty() {
86                            integer.ok_or(dec_error)?
87                        } else {
88                            // makes sure we don't go beyond 18 decimals
89                            let fract_pow = units
90                                .checked_sub(Uint::from(fract.len()))
91                                .ok_or(dec_error)?;
92
93                            let fract = Uint::from_dec_str(fract)?
94                                .checked_mul(Uint::from(10u32).pow(fract_pow))
95                                .ok_or_else(|| {
96                                    Error::Other(Cow::Owned(original_dec_error.clone()))
97                                })?;
98
99                            integer
100                                .and_then(|integer| integer.checked_add(fract))
101                                .ok_or(Error::Other(Cow::Owned(original_dec_error)))?
102                        }
103                    }
104                    None => return Err(dec_error.into()),
105                }
106            }
107        };
108
109        Ok(uint.into())
110    }
111
112    // We don't have a proper signed int 256-bit long type, so here we're cheating. We build a U256
113    // out of it and check that it's within the lower/upper bound of a hypothetical I256 type: half
114    // the `U256::max_value().
115    fn tokenize_int(value: &str) -> Result<[u8; 32], Error> {
116        let result = StrictTokenizer::tokenize_int(value);
117        if result.is_ok() {
118            return result;
119        }
120
121        let abs = Uint::from_dec_str(value.trim_start_matches('-'))?;
122        let max = Uint::max_value() / 2;
123        let int = if value.starts_with('-') {
124            if abs.is_zero() {
125                return Ok(abs.into());
126            } else if abs > max + 1 {
127                return Err(Error::Other(Cow::Borrowed("int256 parse error: Underflow")));
128            }
129            !abs + 1 // two's complement
130        } else {
131            if abs > max {
132                return Err(Error::Other(Cow::Borrowed("int256 parse error: Overflow")));
133            }
134            abs
135        };
136        Ok(int.into())
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use ethereum_types::FromDecStrErr;
143
144    use crate::{
145        errors::Error,
146        token::{LenientTokenizer, Token, Tokenizer},
147        ParamType, Uint,
148    };
149
150    #[test]
151    fn tokenize_uint() {
152        assert_eq!(
153            LenientTokenizer::tokenize(
154                &ParamType::Uint(256),
155                "1111111111111111111111111111111111111111111111111111111111111111"
156            )
157            .unwrap(),
158            Token::Uint([0x11u8; 32].into())
159        );
160    }
161
162    #[test]
163    fn tokenize_uint_wei() {
164        assert_eq!(
165            LenientTokenizer::tokenize(&ParamType::Uint(256), "1wei").unwrap(),
166            Token::Uint(Uint::from(1))
167        );
168
169        assert_eq!(
170            LenientTokenizer::tokenize(&ParamType::Uint(256), "1 wei").unwrap(),
171            Token::Uint(Uint::from(1))
172        );
173    }
174
175    #[test]
176    fn tokenize_uint_gwei() {
177        assert_eq!(
178            LenientTokenizer::tokenize(&ParamType::Uint(256), "1nano").unwrap(),
179            Token::Uint(Uint::from_dec_str("1000000000").unwrap())
180        );
181
182        assert_eq!(
183            LenientTokenizer::tokenize(&ParamType::Uint(256), "1nanoether").unwrap(),
184            Token::Uint(Uint::from_dec_str("1000000000").unwrap())
185        );
186
187        assert_eq!(
188            LenientTokenizer::tokenize(&ParamType::Uint(256), "1gwei").unwrap(),
189            Token::Uint(Uint::from_dec_str("1000000000").unwrap())
190        );
191
192        assert_eq!(
193            LenientTokenizer::tokenize(&ParamType::Uint(256), "0.1 gwei").unwrap(),
194            Token::Uint(Uint::from_dec_str("100000000").unwrap())
195        );
196    }
197
198    #[test]
199    fn tokenize_uint_ether() {
200        assert_eq!(
201            LenientTokenizer::tokenize(&ParamType::Uint(256), "10000000000ether").unwrap(),
202            Token::Uint(Uint::from_dec_str("10000000000000000000000000000").unwrap())
203        );
204
205        assert_eq!(
206            LenientTokenizer::tokenize(&ParamType::Uint(256), "1ether").unwrap(),
207            Token::Uint(Uint::from_dec_str("1000000000000000000").unwrap())
208        );
209
210        assert_eq!(
211            LenientTokenizer::tokenize(&ParamType::Uint(256), "0.01 ether").unwrap(),
212            Token::Uint(Uint::from_dec_str("10000000000000000").unwrap())
213        );
214
215        assert_eq!(
216            LenientTokenizer::tokenize(&ParamType::Uint(256), "0.000000000000000001ether").unwrap(),
217            Token::Uint(Uint::from_dec_str("1").unwrap())
218        );
219
220        assert_eq!(
221            LenientTokenizer::tokenize(&ParamType::Uint(256), "0.000000000000000001ether").unwrap(),
222            LenientTokenizer::tokenize(&ParamType::Uint(256), "1wei").unwrap(),
223        );
224    }
225
226    #[test]
227    fn tokenize_uint_array_ether() {
228        assert_eq!(
229            LenientTokenizer::tokenize(
230                &ParamType::Array(Box::new(ParamType::Uint(256))),
231                "[1ether,0.1 ether]"
232            )
233            .unwrap(),
234            Token::Array(vec![
235                Token::Uint(Uint::from_dec_str("1000000000000000000").unwrap()),
236                Token::Uint(Uint::from_dec_str("100000000000000000").unwrap())
237            ])
238        );
239    }
240
241    #[test]
242    fn tokenize_uint_invalid_units() {
243        let _error = Error::from(FromDecStrErr::InvalidCharacter);
244
245        assert!(matches!(
246            LenientTokenizer::tokenize(&ParamType::Uint(256), "0.1 wei"),
247            Err(_error)
248        ));
249
250        // 0.1 wei
251        assert!(matches!(
252            LenientTokenizer::tokenize(&ParamType::Uint(256), "0.0000000000000000001ether"),
253            Err(_error)
254        ));
255
256        // 1 ether + 0.1 wei
257        assert!(matches!(
258            LenientTokenizer::tokenize(&ParamType::Uint(256), "1.0000000000000000001ether"),
259            Err(_error)
260        ));
261
262        // 1_000_000_000 ether + 0.1 wei
263        assert!(matches!(
264            LenientTokenizer::tokenize(
265                &ParamType::Uint(256),
266                "1000000000.0000000000000000001ether"
267            ),
268            Err(_error)
269        ));
270
271        assert!(matches!(
272            LenientTokenizer::tokenize(&ParamType::Uint(256), "0..1 gwei"),
273            Err(_error)
274        ));
275
276        assert!(matches!(
277            LenientTokenizer::tokenize(&ParamType::Uint(256), "..1 gwei"),
278            Err(_error)
279        ));
280
281        assert!(matches!(
282            LenientTokenizer::tokenize(&ParamType::Uint(256), "1. gwei"),
283            Err(_error)
284        ));
285
286        assert!(matches!(
287            LenientTokenizer::tokenize(&ParamType::Uint(256), ".1 gwei"),
288            Err(_error)
289        ));
290
291        assert!(matches!(
292            LenientTokenizer::tokenize(&ParamType::Uint(256), "2.1.1 gwei"),
293            Err(_error)
294        ));
295
296        assert!(matches!(
297            LenientTokenizer::tokenize(&ParamType::Uint(256), ".1.1 gwei"),
298            Err(_error)
299        ));
300
301        assert!(matches!(
302            LenientTokenizer::tokenize(&ParamType::Uint(256), "1abc"),
303            Err(_error)
304        ));
305
306        assert!(matches!(
307            LenientTokenizer::tokenize(&ParamType::Uint(256), "1 gwei "),
308            Err(_error)
309        ));
310
311        assert!(matches!(
312            LenientTokenizer::tokenize(&ParamType::Uint(256), "g 1 gwei"),
313            Err(_error)
314        ));
315
316        assert!(matches!(
317            LenientTokenizer::tokenize(&ParamType::Uint(256), "1gwei 1 gwei"),
318            Err(_error)
319        ));
320    }
321}