near_runtime_utils/
lib.rs

1//! Contains utility functions for runtime.
2#[macro_use]
3extern crate lazy_static;
4
5use regex::Regex;
6
7type AccountId = String;
8
9pub const MIN_ACCOUNT_ID_LEN: usize = 2;
10pub const MAX_ACCOUNT_ID_LEN: usize = 64;
11
12lazy_static! {
13    /// See NEP#0006
14    static ref VALID_ACCOUNT_ID: Regex =
15        Regex::new(r"^(([a-z\d]+[\-_])*[a-z\d]+\.)*([a-z\d]+[\-_])*[a-z\d]+$").unwrap();
16    /// Represents a part of an account ID with a suffix of as a separator `.`.
17    static ref VALID_ACCOUNT_PART_ID_WITH_TAIL_SEPARATOR: Regex =
18        Regex::new(r"^([a-z\d]+[\-_])*[a-z\d]+\.$").unwrap();
19    /// Represents a top level account ID.
20    static ref VALID_TOP_LEVEL_ACCOUNT_ID: Regex =
21        Regex::new(r"^([a-z\d]+[\-_])*[a-z\d]+$").unwrap();
22}
23
24/// const does not allow function call, so have to resort to this
25pub fn system_account() -> AccountId {
26    "system".to_string()
27}
28
29pub fn is_valid_account_id(account_id: &AccountId) -> bool {
30    account_id.len() >= MIN_ACCOUNT_ID_LEN
31        && account_id.len() <= MAX_ACCOUNT_ID_LEN
32        && VALID_ACCOUNT_ID.is_match(account_id)
33}
34
35pub fn is_valid_top_level_account_id(account_id: &AccountId) -> bool {
36    account_id.len() >= MIN_ACCOUNT_ID_LEN
37        && account_id.len() <= MAX_ACCOUNT_ID_LEN
38        && account_id != &system_account()
39        && VALID_TOP_LEVEL_ACCOUNT_ID.is_match(account_id)
40}
41
42/// Returns true if the signer_id can create a direct sub-account with the given account Id.
43/// It assumes the signer_id is a valid account_id
44pub fn is_valid_sub_account_id(signer_id: &AccountId, sub_account_id: &AccountId) -> bool {
45    if !is_valid_account_id(sub_account_id) {
46        return false;
47    }
48    if signer_id.len() >= sub_account_id.len() {
49        return false;
50    }
51    // Will not panic, since valid account id is utf-8 only and the length is checked above.
52    // e.g. when `near` creates `aa.near`, it splits into `aa.` and `near`
53    let (prefix, suffix) = sub_account_id.split_at(sub_account_id.len() - signer_id.len());
54    if suffix != signer_id {
55        return false;
56    }
57    VALID_ACCOUNT_PART_ID_WITH_TAIL_SEPARATOR.is_match(prefix)
58}
59
60/// Returns true if the account ID length is 64 characters and it's a hex representation.
61pub fn is_account_id_64_len_hex(account_id: &str) -> bool {
62    account_id.len() == 64
63        && account_id.as_bytes().iter().all(|&b| match b {
64            b'a'..=b'f' | b'0'..=b'9' => true,
65            _ => false,
66        })
67}
68
69/// Returns true if the account ID is suppose to be EVM machine.
70pub fn is_account_evm(account_id: &str) -> bool {
71    account_id == "evm"
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    const OK_ACCOUNT_IDS: &[&str] = &[
79        "aa",
80        "a-a",
81        "a-aa",
82        "100",
83        "0o",
84        "com",
85        "near",
86        "bowen",
87        "b-o_w_e-n",
88        "b.owen",
89        "bro.wen",
90        "a.ha",
91        "a.b-a.ra",
92        "system",
93        "over.9000",
94        "google.com",
95        "illia.cheapaccounts.near",
96        "0o0ooo00oo00o",
97        "alex-skidanov",
98        "10-4.8-2",
99        "b-o_w_e-n",
100        "no_lols",
101        "0123456789012345678901234567890123456789012345678901234567890123",
102        // Valid, but can't be created
103        "near.a",
104    ];
105
106    #[test]
107    fn test_is_valid_account_id() {
108        for account_id in OK_ACCOUNT_IDS {
109            assert!(
110                is_valid_account_id(&account_id.to_string()),
111                "Valid account id {:?} marked invalid",
112                account_id
113            );
114        }
115
116        let bad_account_ids = vec![
117            "a",
118            "A",
119            "Abc",
120            "-near",
121            "near-",
122            "-near-",
123            "near.",
124            ".near",
125            "near@",
126            "@near",
127            "неар",
128            "@@@@@",
129            "0__0",
130            "0_-_0",
131            "0_-_0",
132            "..",
133            "a..near",
134            "nEar",
135            "_bowen",
136            "hello world",
137            "abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz",
138            "01234567890123456789012345678901234567890123456789012345678901234",
139            // `@` separators are banned now
140            "some-complex-address@gmail.com",
141            "sub.buy_d1gitz@atata@b0-rg.c_0_m",
142        ];
143        for account_id in bad_account_ids {
144            assert!(
145                !is_valid_account_id(&account_id.to_string()),
146                "Invalid account id {:?} marked valid",
147                account_id
148            );
149        }
150    }
151
152    #[test]
153    fn test_is_valid_top_level_account_id() {
154        let ok_top_level_account_ids = vec![
155            "aa",
156            "a-a",
157            "a-aa",
158            "100",
159            "0o",
160            "com",
161            "near",
162            "bowen",
163            "b-o_w_e-n",
164            "0o0ooo00oo00o",
165            "alex-skidanov",
166            "b-o_w_e-n",
167            "no_lols",
168            "0123456789012345678901234567890123456789012345678901234567890123",
169        ];
170        for account_id in ok_top_level_account_ids {
171            assert!(
172                is_valid_top_level_account_id(&account_id.to_string()),
173                "Valid top level account id {:?} marked invalid",
174                account_id
175            );
176        }
177
178        let bad_top_level_account_ids = vec![
179            "near.a",
180            "b.owen",
181            "bro.wen",
182            "a.ha",
183            "a.b-a.ra",
184            "some-complex-address@gmail.com",
185            "sub.buy_d1gitz@atata@b0-rg.c_0_m",
186            "over.9000",
187            "google.com",
188            "illia.cheapaccounts.near",
189            "10-4.8-2",
190            "a",
191            "A",
192            "Abc",
193            "-near",
194            "near-",
195            "-near-",
196            "near.",
197            ".near",
198            "near@",
199            "@near",
200            "неар",
201            "@@@@@",
202            "0__0",
203            "0_-_0",
204            "0_-_0",
205            "..",
206            "a..near",
207            "nEar",
208            "_bowen",
209            "hello world",
210            "abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz",
211            "01234567890123456789012345678901234567890123456789012345678901234",
212            // Valid regex and length, but reserved
213            "system",
214        ];
215        for account_id in bad_top_level_account_ids {
216            assert!(
217                !is_valid_top_level_account_id(&account_id.to_string()),
218                "Invalid top level account id {:?} marked valid",
219                account_id
220            );
221        }
222    }
223
224    #[test]
225    fn test_is_valid_sub_account_id() {
226        let ok_pairs = vec![
227            ("test", "a.test"),
228            ("test-me", "abc.test-me"),
229            ("gmail.com", "abc.gmail.com"),
230            ("gmail.com", "abc-lol.gmail.com"),
231            ("gmail.com", "abc_lol.gmail.com"),
232            ("gmail.com", "bro-abc_lol.gmail.com"),
233            ("g0", "0g.g0"),
234            ("1g", "1g.1g"),
235            ("5-3", "4_2.5-3"),
236        ];
237        for (signer_id, sub_account_id) in ok_pairs {
238            assert!(
239                is_valid_sub_account_id(&signer_id.to_string(), &sub_account_id.to_string()),
240                "Failed to create sub-account {:?} by account {:?}",
241                sub_account_id,
242                signer_id
243            );
244        }
245
246        let bad_pairs = vec![
247            ("test", ".test"),
248            ("test", "test"),
249            ("test", "est"),
250            ("test", ""),
251            ("test", "st"),
252            ("test5", "ббб"),
253            ("test", "a-test"),
254            ("test", "etest"),
255            ("test", "a.etest"),
256            ("test", "retest"),
257            ("test-me", "abc-.test-me"),
258            ("test-me", "Abc.test-me"),
259            ("test-me", "-abc.test-me"),
260            ("test-me", "a--c.test-me"),
261            ("test-me", "a_-c.test-me"),
262            ("test-me", "a-_c.test-me"),
263            ("test-me", "_abc.test-me"),
264            ("test-me", "abc_.test-me"),
265            ("test-me", "..test-me"),
266            ("test-me", "a..test-me"),
267            ("gmail.com", "a.abc@gmail.com"),
268            ("gmail.com", ".abc@gmail.com"),
269            ("gmail.com", ".abc@gmail@com"),
270            ("gmail.com", "abc@gmail@com"),
271            ("test", "a@test"),
272            ("test_me", "abc@test_me"),
273            ("gmail.com", "abc@gmail.com"),
274            ("gmail@com", "abc.gmail@com"),
275            ("gmail.com", "abc-lol@gmail.com"),
276            ("gmail@com", "abc_lol.gmail@com"),
277            ("gmail@com", "bro-abc_lol.gmail@com"),
278            ("gmail.com", "123456789012345678901234567890123456789012345678901234567890@gmail.com"),
279            (
280                "123456789012345678901234567890123456789012345678901234567890",
281                "1234567890.123456789012345678901234567890123456789012345678901234567890",
282            ),
283            ("aa", "ъ@aa"),
284            ("aa", "ъ.aa"),
285        ];
286        for (signer_id, sub_account_id) in bad_pairs {
287            assert!(
288                !is_valid_sub_account_id(&signer_id.to_string(), &sub_account_id.to_string()),
289                "Invalid sub-account {:?} created by account {:?}",
290                sub_account_id,
291                signer_id
292            );
293        }
294    }
295
296    #[test]
297    fn test_is_account_id_64_len_hex() {
298        let valid_64_len_hex_account_ids = vec![
299            "0000000000000000000000000000000000000000000000000000000000000000",
300            "6174617461746174617461746174617461746174617461746174617461746174",
301            "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
302            "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
303            "20782e20662e64666420482123494b6b6c677573646b6c66676a646b6c736667",
304        ];
305        for valid_account_id in valid_64_len_hex_account_ids {
306            assert!(
307                is_account_id_64_len_hex(valid_account_id),
308                "Account ID {} should be valid 64-len hex",
309                valid_account_id
310            );
311        }
312
313        let invalid_64_len_hex_account_ids = vec![
314            "000000000000000000000000000000000000000000000000000000000000000",
315            "6.74617461746174617461746174617461746174617461746174617461746174",
316            "012-456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
317            "fffff_ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
318            "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
319            "00000000000000000000000000000000000000000000000000000000000000",
320        ];
321        for invalid_account_id in invalid_64_len_hex_account_ids {
322            assert!(
323                !is_account_id_64_len_hex(invalid_account_id),
324                "Account ID {} should be invalid 64-len hex",
325                invalid_account_id
326            );
327        }
328    }
329}