1#[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 static ref VALID_ACCOUNT_ID: Regex =
15 Regex::new(r"^(([a-z\d]+[\-_])*[a-z\d]+\.)*([a-z\d]+[\-_])*[a-z\d]+$").unwrap();
16 static ref VALID_ACCOUNT_PART_ID_WITH_TAIL_SEPARATOR: Regex =
18 Regex::new(r"^([a-z\d]+[\-_])*[a-z\d]+\.$").unwrap();
19 static ref VALID_TOP_LEVEL_ACCOUNT_ID: Regex =
21 Regex::new(r"^([a-z\d]+[\-_])*[a-z\d]+$").unwrap();
22}
23
24pub 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
42pub 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 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
60pub 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
69pub 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 "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 "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 "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}