1use std::borrow::Cow;
2
3use crate::{AccountId, ParseAccountError};
4
5#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
29#[cfg_attr(feature = "abi", derive(borsh::BorshSchema))]
30pub struct AccountIdRef(pub(crate) str);
31
32#[derive(PartialEq)]
39pub enum AccountType {
40 NamedAccount,
42 NearImplicitAccount,
44 EthImplicitAccount,
46 NearDeterministicAccount,
50}
51
52impl AccountType {
53 pub fn is_implicit(&self) -> bool {
54 match &self {
55 Self::NearImplicitAccount => true,
56 Self::EthImplicitAccount => true,
57 Self::NearDeterministicAccount => true,
58 Self::NamedAccount => false,
59 }
60 }
61}
62
63impl AccountIdRef {
64 pub const MIN_LEN: usize = crate::validation::MIN_LEN;
66 pub const MAX_LEN: usize = crate::validation::MAX_LEN;
68
69 pub fn new<S: AsRef<str> + ?Sized>(id: &S) -> Result<&Self, ParseAccountError> {
73 let id = id.as_ref();
74 crate::validation::validate(id)?;
75
76 Ok(unsafe { &*(id as *const str as *const Self) })
80 }
81
82 pub const fn new_or_panic(id: &str) -> &Self {
89 crate::validation::validate_const(id);
90
91 unsafe { &*(id as *const str as *const Self) }
92 }
93
94 pub(crate) fn new_unvalidated<S: AsRef<str> + ?Sized>(id: &S) -> &Self {
99 let id = id.as_ref();
100 #[cfg(not(feature = "internal_unstable"))]
105 debug_assert!(crate::validation::validate(id).is_ok());
106
107 unsafe { &*(id as *const str as *const Self) }
109 }
110
111 pub fn as_bytes(&self) -> &[u8] {
113 self.0.as_bytes()
114 }
115
116 pub fn as_str(&self) -> &str {
127 &self.0
128 }
129
130 pub fn is_top_level(&self) -> bool {
147 !self.is_system() && !self.0.contains('.')
148 }
149
150 pub fn is_sub_account_of(&self, parent: &AccountIdRef) -> bool {
173 self.0
174 .strip_suffix(parent.as_str())
175 .and_then(|s| s.strip_suffix('.'))
176 .map_or(false, |s| !s.contains('.'))
177 }
178
179 pub fn get_account_type(&self) -> AccountType {
204 if crate::validation::is_eth_implicit(self.as_str()) {
205 return AccountType::EthImplicitAccount;
206 }
207 if crate::validation::is_near_implicit(self.as_str()) {
208 return AccountType::NearImplicitAccount;
209 }
210 if crate::validation::is_near_deterministic(self.as_str()) {
211 return AccountType::NearDeterministicAccount;
212 }
213 AccountType::NamedAccount
214 }
215
216 pub fn is_system(&self) -> bool {
232 self == "system"
233 }
234
235 pub const fn len(&self) -> usize {
237 self.0.len()
238 }
239
240 pub fn get_parent_account_id(&self) -> Option<&AccountIdRef> {
260 let parent_str = self.as_str().split_once('.')?.1;
261 Some(AccountIdRef::new_unvalidated(parent_str))
262 }
263}
264
265impl std::fmt::Display for AccountIdRef {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 std::fmt::Display::fmt(&self.0, f)
268 }
269}
270
271impl ToOwned for AccountIdRef {
272 type Owned = AccountId;
273
274 fn to_owned(&self) -> Self::Owned {
275 AccountId(self.0.into())
276 }
277}
278
279impl<'a> From<&'a AccountIdRef> for AccountId {
280 fn from(id: &'a AccountIdRef) -> Self {
281 id.to_owned()
282 }
283}
284
285impl<'s> TryFrom<&'s str> for &'s AccountIdRef {
286 type Error = ParseAccountError;
287
288 fn try_from(value: &'s str) -> Result<Self, Self::Error> {
289 AccountIdRef::new(value)
290 }
291}
292
293impl AsRef<str> for AccountIdRef {
294 fn as_ref(&self) -> &str {
295 &self.0
296 }
297}
298
299impl PartialEq<AccountIdRef> for String {
300 fn eq(&self, other: &AccountIdRef) -> bool {
301 self == &other.0
302 }
303}
304
305impl PartialEq<String> for AccountIdRef {
306 fn eq(&self, other: &String) -> bool {
307 &self.0 == other
308 }
309}
310
311impl PartialEq<AccountIdRef> for str {
312 fn eq(&self, other: &AccountIdRef) -> bool {
313 self == &other.0
314 }
315}
316
317impl PartialEq<str> for AccountIdRef {
318 fn eq(&self, other: &str) -> bool {
319 &self.0 == other
320 }
321}
322
323impl<'a> PartialEq<AccountIdRef> for &'a str {
324 fn eq(&self, other: &AccountIdRef) -> bool {
325 *self == &other.0
326 }
327}
328
329impl<'a> PartialEq<&'a str> for AccountIdRef {
330 fn eq(&self, other: &&'a str) -> bool {
331 &self.0 == *other
332 }
333}
334
335impl<'a> PartialEq<&'a AccountIdRef> for str {
336 fn eq(&self, other: &&'a AccountIdRef) -> bool {
337 self == &other.0
338 }
339}
340
341impl<'a> PartialEq<str> for &'a AccountIdRef {
342 fn eq(&self, other: &str) -> bool {
343 &self.0 == other
344 }
345}
346
347impl<'a> PartialEq<&'a AccountIdRef> for String {
348 fn eq(&self, other: &&'a AccountIdRef) -> bool {
349 self == &other.0
350 }
351}
352
353impl<'a> PartialEq<String> for &'a AccountIdRef {
354 fn eq(&self, other: &String) -> bool {
355 &self.0 == other
356 }
357}
358
359impl PartialOrd<AccountIdRef> for String {
360 fn partial_cmp(&self, other: &AccountIdRef) -> Option<std::cmp::Ordering> {
361 self.as_str().partial_cmp(&other.0)
362 }
363}
364
365impl PartialOrd<String> for AccountIdRef {
366 fn partial_cmp(&self, other: &String) -> Option<std::cmp::Ordering> {
367 self.0.partial_cmp(other.as_str())
368 }
369}
370
371impl PartialOrd<AccountIdRef> for str {
372 fn partial_cmp(&self, other: &AccountIdRef) -> Option<std::cmp::Ordering> {
373 self.partial_cmp(other.as_str())
374 }
375}
376
377impl PartialOrd<str> for AccountIdRef {
378 fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
379 self.as_str().partial_cmp(other)
380 }
381}
382
383impl<'a> PartialOrd<AccountIdRef> for &'a str {
384 fn partial_cmp(&self, other: &AccountIdRef) -> Option<std::cmp::Ordering> {
385 self.partial_cmp(&other.as_str())
386 }
387}
388
389impl<'a> PartialOrd<&'a str> for AccountIdRef {
390 fn partial_cmp(&self, other: &&'a str) -> Option<std::cmp::Ordering> {
391 self.as_str().partial_cmp(*other)
392 }
393}
394
395impl<'a> PartialOrd<&'a AccountIdRef> for String {
396 fn partial_cmp(&self, other: &&'a AccountIdRef) -> Option<std::cmp::Ordering> {
397 self.as_str().partial_cmp(&other.0)
398 }
399}
400
401impl<'a> PartialOrd<String> for &'a AccountIdRef {
402 fn partial_cmp(&self, other: &String) -> Option<std::cmp::Ordering> {
403 self.0.partial_cmp(other.as_str())
404 }
405}
406
407impl<'a> PartialOrd<&'a AccountIdRef> for str {
408 fn partial_cmp(&self, other: &&'a AccountIdRef) -> Option<std::cmp::Ordering> {
409 self.partial_cmp(other.as_str())
410 }
411}
412
413impl<'a> PartialOrd<str> for &'a AccountIdRef {
414 fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
415 self.as_str().partial_cmp(other)
416 }
417}
418
419impl<'a> From<&'a AccountIdRef> for Cow<'a, AccountIdRef> {
420 fn from(value: &'a AccountIdRef) -> Self {
421 Cow::Borrowed(value)
422 }
423}
424
425#[cfg(feature = "schemars-v0_8")]
426impl schemars_v0_8::JsonSchema for AccountIdRef {
427 fn is_referenceable() -> bool {
428 false
429 }
430
431 fn schema_name() -> String {
432 "AccountIdRef".to_string()
433 }
434
435 fn json_schema(_: &mut schemars_v0_8::gen::SchemaGenerator) -> schemars_v0_8::schema::Schema {
436 use schemars_v0_8::schema::{InstanceType, Metadata, Schema, SchemaObject, SingleOrVec};
437 Schema::Object(SchemaObject {
438 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
439 metadata: Some(Box::new(Metadata {
440 description: Some("Account identifier. This is the human readable UTF-8 string which is used internally to index accounts on the network and their respective state.\n\nThis is the \"referenced\" version of the account ID. It is to [`AccountId`] what [`str`] is to [`String`], and works quite similarly to [`Path`]. Like with [`str`] and [`Path`], you can't have a value of type `AccountIdRef`, but you can have a reference like `&AccountIdRef` or `&mut AccountIdRef`.\n\nThis type supports zero-copy deserialization offered by [`serde`](https://docs.rs/serde/), but cannot do the same for [`borsh`](https://docs.rs/borsh/) since the latter does not support zero-copy.\n\n# Examples ``` use near_account_id::{AccountId, AccountIdRef}; use std::convert::{TryFrom, TryInto};\n\n// Construction let alice = AccountIdRef::new(\"alice.near\").unwrap(); assert!(AccountIdRef::new(\"invalid.\").is_err()); ```\n\n[`FromStr`]: std::str::FromStr [`Path`]: std::path::Path".to_string()),
441 ..Default::default()
442 })),
443 ..Default::default()
444 })
445 }
446}
447
448#[cfg(feature = "schemars-v1")]
449impl schemars_v1::JsonSchema for AccountIdRef {
450 fn schema_name() -> std::borrow::Cow<'static, str> {
451 "AccountIdRef".to_string().into()
452 }
453
454 fn json_schema(_: &mut schemars_v1::SchemaGenerator) -> schemars_v1::Schema {
455 schemars_v1::json_schema!({
456 "$schema": "https://json-schema.org/draft/2020-12/schema",
457 "description": "Account identifier. This is the human readable UTF-8 string which is used internally to index\naccounts on the network and their respective state.\n\nThis is the \"referenced\" version of the account ID. It is to [`AccountId`] what [`str`] is to [`String`],\nand works quite similarly to [`Path`]. Like with [`str`] and [`Path`], you\ncan't have a value of type `AccountIdRef`, but you can have a reference like `&AccountIdRef` or\n`&mut AccountIdRef`.\n\nThis type supports zero-copy deserialization offered by [`serde`](https://docs.rs/serde/), but cannot\ndo the same for [`borsh`](https://docs.rs/borsh/) since the latter does not support zero-copy.\n\n# Examples\n```\nuse near_account_id::{AccountId, AccountIdRef};\nuse std::convert::{TryFrom, TryInto};\n\n// Construction\nlet alice = AccountIdRef::new(\"alice.near\").unwrap();\nassert!(AccountIdRef::new(\"invalid.\").is_err());\n```\n\n[`FromStr`]: std::str::FromStr\n[`Path`]: std::path::Path",
458 "title": "AccountIdRef",
459 "type": "string"
460 })
461 }
462}
463
464#[cfg(feature = "arbitrary")]
465impl<'a> arbitrary::Arbitrary<'a> for &'a AccountIdRef {
466 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
467 (crate::validation::MIN_LEN, Some(crate::validation::MAX_LEN))
468 }
469
470 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
471 let mut s = u.arbitrary::<&str>()?;
472
473 loop {
474 match AccountIdRef::new(s) {
475 Ok(account_id) => break Ok(account_id),
476 Err(ParseAccountError {
477 char: Some((idx, _)),
478 ..
479 }) => {
480 s = &s[..idx];
481 continue;
482 }
483 _ => break Err(arbitrary::Error::IncorrectFormat),
484 }
485 }
486 }
487
488 fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
489 let s = <&str as arbitrary::Arbitrary>::arbitrary_take_rest(u)?;
490 AccountIdRef::new(s).map_err(|_| arbitrary::Error::IncorrectFormat)
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use crate::ParseErrorKind;
497
498 use super::*;
499
500 #[test]
501 #[cfg(feature = "schemars-v1")]
502 fn test_schemars_v1() {
503 let schema = schemars_v1::schema_for!(AccountIdRef);
504 let json_schema = serde_json::to_value(&schema).unwrap();
505 dbg!(&json_schema);
506 assert_eq!(
507 json_schema,
508 serde_json::json!({
509 "$schema": "https://json-schema.org/draft/2020-12/schema",
510 "description": "Account identifier. This is the human readable UTF-8 string which is used internally to index\naccounts on the network and their respective state.\n\nThis is the \"referenced\" version of the account ID. It is to [`AccountId`] what [`str`] is to [`String`],\nand works quite similarly to [`Path`]. Like with [`str`] and [`Path`], you\ncan't have a value of type `AccountIdRef`, but you can have a reference like `&AccountIdRef` or\n`&mut AccountIdRef`.\n\nThis type supports zero-copy deserialization offered by [`serde`](https://docs.rs/serde/), but cannot\ndo the same for [`borsh`](https://docs.rs/borsh/) since the latter does not support zero-copy.\n\n# Examples\n```\nuse near_account_id::{AccountId, AccountIdRef};\nuse std::convert::{TryFrom, TryInto};\n\n// Construction\nlet alice = AccountIdRef::new(\"alice.near\").unwrap();\nassert!(AccountIdRef::new(\"invalid.\").is_err());\n```\n\n[`FromStr`]: std::str::FromStr\n[`Path`]: std::path::Path",
511 "title": "AccountIdRef",
512 "type": "string"
513 }
514 )
515 );
516 }
517
518 #[test]
519 #[cfg(feature = "schemars-v0_8")]
520 fn test_schemars_v0_8() {
521 let schema = schemars_v0_8::schema_for!(AccountIdRef);
522 let json_schema = serde_json::to_value(&schema).unwrap();
523 dbg!(&json_schema);
524 assert_eq!(
525 json_schema,
526 serde_json::json!({
527 "$schema": "http://json-schema.org/draft-07/schema#",
528 "description": "Account identifier. This is the human readable UTF-8 string which is used internally to index accounts on the network and their respective state.\n\nThis is the \"referenced\" version of the account ID. It is to [`AccountId`] what [`str`] is to [`String`], and works quite similarly to [`Path`]. Like with [`str`] and [`Path`], you can't have a value of type `AccountIdRef`, but you can have a reference like `&AccountIdRef` or `&mut AccountIdRef`.\n\nThis type supports zero-copy deserialization offered by [`serde`](https://docs.rs/serde/), but cannot do the same for [`borsh`](https://docs.rs/borsh/) since the latter does not support zero-copy.\n\n# Examples ``` use near_account_id::{AccountId, AccountIdRef}; use std::convert::{TryFrom, TryInto};\n\n// Construction let alice = AccountIdRef::new(\"alice.near\").unwrap(); assert!(AccountIdRef::new(\"invalid.\").is_err()); ```\n\n[`FromStr`]: std::str::FromStr [`Path`]: std::path::Path",
529 "title": "AccountIdRef",
530 "type": "string"
531 }
532 )
533 );
534 }
535
536 #[test]
537 fn test_err_kind_classification() {
538 let id = AccountIdRef::new("ErinMoriarty.near");
539 debug_assert!(
540 matches!(
541 id,
542 Err(ParseAccountError {
543 kind: ParseErrorKind::InvalidChar,
544 char: Some((0, 'E'))
545 })
546 ),
547 "{:?}",
548 id
549 );
550
551 let id = AccountIdRef::new("-KarlUrban.near");
552 debug_assert!(
553 matches!(
554 id,
555 Err(ParseAccountError {
556 kind: ParseErrorKind::RedundantSeparator,
557 char: Some((0, '-'))
558 })
559 ),
560 "{:?}",
561 id
562 );
563
564 let id = AccountIdRef::new("anthonystarr.");
565 debug_assert!(
566 matches!(
567 id,
568 Err(ParseAccountError {
569 kind: ParseErrorKind::RedundantSeparator,
570 char: Some((12, '.'))
571 })
572 ),
573 "{:?}",
574 id
575 );
576
577 let id = AccountIdRef::new("jack__Quaid.near");
578 debug_assert!(
579 matches!(
580 id,
581 Err(ParseAccountError {
582 kind: ParseErrorKind::RedundantSeparator,
583 char: Some((5, '_'))
584 })
585 ),
586 "{:?}",
587 id
588 );
589 }
590
591 #[test]
592 fn test_is_valid_top_level_account_id() {
593 let ok_top_level_account_ids = &[
594 "aa",
595 "a-a",
596 "a-aa",
597 "100",
598 "0o",
599 "com",
600 "near",
601 "bowen",
602 "b-o_w_e-n",
603 "0o0ooo00oo00o",
604 "alex-skidanov",
605 "b-o_w_e-n",
606 "no_lols",
607 "0xb794f5ea0ba39494ce839613fffba74279579268",
609 "0123456789012345678901234567890123456789012345678901234567890123",
611 ];
612 for account_id in ok_top_level_account_ids {
613 assert!(
614 AccountIdRef::new(account_id).map_or(false, |account_id| account_id.is_top_level()),
615 "Valid top level account id {:?} marked invalid",
616 account_id
617 );
618 }
619
620 let bad_top_level_account_ids = &[
621 "ƒelicia.near", "near.a",
623 "b.owen",
624 "bro.wen",
625 "a.ha",
626 "a.b-a.ra",
627 "some-complex-address@gmail.com",
628 "sub.buy_d1gitz@atata@b0-rg.c_0_m",
629 "over.9000",
630 "google.com",
631 "illia.cheapaccounts.near",
632 "10-4.8-2",
633 "a",
634 "A",
635 "Abc",
636 "-near",
637 "near-",
638 "-near-",
639 "near.",
640 ".near",
641 "near@",
642 "@near",
643 "неар",
644 "@@@@@",
645 "0__0",
646 "0_-_0",
647 "0_-_0",
648 "..",
649 "a..near",
650 "nEar",
651 "_bowen",
652 "hello world",
653 "abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz",
654 "01234567890123456789012345678901234567890123456789012345678901234",
655 "system",
657 ];
658 for account_id in bad_top_level_account_ids {
659 assert!(
660 !AccountIdRef::new(account_id)
661 .map_or(false, |account_id| account_id.is_top_level()),
662 "Invalid top level account id {:?} marked valid",
663 account_id
664 );
665 }
666 }
667
668 #[test]
669 fn test_is_valid_sub_account_id() {
670 let ok_pairs = &[
671 ("test", "a.test"),
672 ("test-me", "abc.test-me"),
673 ("gmail.com", "abc.gmail.com"),
674 ("gmail.com", "abc-lol.gmail.com"),
675 ("gmail.com", "abc_lol.gmail.com"),
676 ("gmail.com", "bro-abc_lol.gmail.com"),
677 ("g0", "0g.g0"),
678 ("1g", "1g.1g"),
679 ("5-3", "4_2.5-3"),
680 ];
681 for (signer_id, sub_account_id) in ok_pairs {
682 assert!(
683 matches!(
684 (AccountIdRef::new(signer_id), AccountIdRef::new(sub_account_id)),
685 (Ok(signer_id), Ok(sub_account_id)) if sub_account_id.is_sub_account_of(signer_id)
686 ),
687 "Failed to create sub-account {:?} by account {:?}",
688 sub_account_id,
689 signer_id
690 );
691 }
692
693 let bad_pairs = &[
694 ("test", ".test"),
695 ("test", "test"),
696 ("test", "a1.a.test"),
697 ("test", "est"),
698 ("test", ""),
699 ("test", "st"),
700 ("test5", "ббб"),
701 ("test", "a-test"),
702 ("test", "etest"),
703 ("test", "a.etest"),
704 ("test", "retest"),
705 ("test-me", "abc-.test-me"),
706 ("test-me", "Abc.test-me"),
707 ("test-me", "-abc.test-me"),
708 ("test-me", "a--c.test-me"),
709 ("test-me", "a_-c.test-me"),
710 ("test-me", "a-_c.test-me"),
711 ("test-me", "_abc.test-me"),
712 ("test-me", "abc_.test-me"),
713 ("test-me", "..test-me"),
714 ("test-me", "a..test-me"),
715 ("gmail.com", "a.abc@gmail.com"),
716 ("gmail.com", ".abc@gmail.com"),
717 ("gmail.com", ".abc@gmail@com"),
718 ("gmail.com", "abc@gmail@com"),
719 ("test", "a@test"),
720 ("test_me", "abc@test_me"),
721 ("gmail.com", "abc@gmail.com"),
722 ("gmail@com", "abc.gmail@com"),
723 ("gmail.com", "abc-lol@gmail.com"),
724 ("gmail@com", "abc_lol.gmail@com"),
725 ("gmail@com", "bro-abc_lol.gmail@com"),
726 (
727 "gmail.com",
728 "123456789012345678901234567890123456789012345678901234567890@gmail.com",
729 ),
730 (
731 "123456789012345678901234567890123456789012345678901234567890",
732 "1234567890.123456789012345678901234567890123456789012345678901234567890",
733 ),
734 (
735 "b794f5ea0ba39494ce839613fffba74279579268",
736 "0xb794f5ea0ba39494ce839613fffba74279579268",
738 ),
739 ("aa", "ъ@aa"),
740 ("aa", "ъ.aa"),
741 ];
742 for (signer_id, sub_account_id) in bad_pairs {
743 assert!(
744 !matches!(
745 (AccountIdRef::new(signer_id), AccountIdRef::new(sub_account_id)),
746 (Ok(signer_id), Ok(sub_account_id)) if sub_account_id.is_sub_account_of(&signer_id)
747 ),
748 "Invalid sub-account {:?} created by account {:?}",
749 sub_account_id,
750 signer_id
751 );
752 }
753 }
754
755 #[test]
756 fn test_is_account_id_near_implicit() {
757 let valid_near_implicit_account_ids = &[
758 "0000000000000000000000000000000000000000000000000000000000000000",
759 "6174617461746174617461746174617461746174617461746174617461746174",
760 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
761 "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
762 "20782e20662e64666420482123494b6b6c677573646b6c66676a646b6c736667",
763 ];
764 for valid_account_id in valid_near_implicit_account_ids {
765 assert!(
766 matches!(
767 AccountIdRef::new(valid_account_id),
768 Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount
769 ),
770 "Account ID {} should be valid 64-len hex",
771 valid_account_id
772 );
773 }
774
775 let invalid_near_implicit_account_ids = &[
776 "000000000000000000000000000000000000000000000000000000000000000",
777 "6.74617461746174617461746174617461746174617461746174617461746174",
778 "012-456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
779 "fffff_ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
780 "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
781 "00000000000000000000000000000000000000000000000000000000000000",
782 ];
783 for invalid_account_id in invalid_near_implicit_account_ids {
784 assert!(
785 !matches!(
786 AccountIdRef::new(invalid_account_id),
787 Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount
788 ),
789 "Account ID {} is not a NEAR-implicit account",
790 invalid_account_id
791 );
792 }
793 }
794
795 #[test]
796 fn test_is_account_id_eth_implicit() {
797 let valid_eth_implicit_account_ids = &[
798 "0x0000000000000000000000000000000000000000",
799 "0x6174617461746174617461746174617461746174",
800 "0x0123456789abcdef0123456789abcdef01234567",
801 "0xffffffffffffffffffffffffffffffffffffffff",
802 "0x20782e20662e64666420482123494b6b6c677573",
803 ];
804 for valid_account_id in valid_eth_implicit_account_ids {
805 assert!(
806 matches!(
807 valid_account_id.parse::<AccountId>(),
808 Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount
809 ),
810 "Account ID {} should be valid 42-len hex, starting with 0x",
811 valid_account_id
812 );
813 }
814
815 let invalid_eth_implicit_account_ids = &[
816 "04b794f5ea0ba39494ce839613fffba74279579268",
817 "0x000000000000000000000000000000000000000",
818 "0x6.74617461746174617461746174617461746174",
819 "0x012-456789abcdef0123456789abcdef01234567",
820 "0xfffff_ffffffffffffffffffffffffffffffffff",
821 "0xoooooooooooooooooooooooooooooooooooooooo",
822 "0x00000000000000000000000000000000000000000",
823 "0000000000000000000000000000000000000000000000000000000000000000",
824 ];
825 for invalid_account_id in invalid_eth_implicit_account_ids {
826 assert!(
827 !matches!(
828 invalid_account_id.parse::<AccountId>(),
829 Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount
830 ),
831 "Account ID {} is not an ETH-implicit account",
832 invalid_account_id
833 );
834 }
835 }
836
837 #[test]
838 fn test_is_account_id_near_deterministic() {
839 let valid_near_deterministic_account_ids = &[
840 "0s0000000000000000000000000000000000000000",
841 "0s6174617461746174617461746174617461746174",
842 "0s0123456789abcdef0123456789abcdef01234567",
843 "0sffffffffffffffffffffffffffffffffffffffff",
844 "0s20782e20662e64666420482123494b6b6c677573",
845 ];
846 for valid_account_id in valid_near_deterministic_account_ids {
847 assert!(
848 matches!(
849 valid_account_id.parse::<AccountId>(),
850 Ok(account_id) if account_id.get_account_type() == AccountType::NearDeterministicAccount
851 ),
852 "Account ID {} should be valid 42-len hex, starting with 0s",
853 valid_account_id
854 );
855 }
856
857 let invalid_near_deterministic_account_ids = &[
858 "00s000000000000000000000000000000000000000",
859 "0s000000000000000000000000000000000000000",
860 "0s00000000000000000000000000000000000000.0",
861 "04b794f5ea0ba39494ce839613fffba74279579268",
862 "0x000000000000000000000000000000000000000",
863 "alice.near",
864 "0s.alice.near",
865 "0s.match_total_len_42_with_a_named_account",
866 ];
867 for invalid_account_id in invalid_near_deterministic_account_ids {
868 assert!(
869 !matches!(
870 invalid_account_id.parse::<AccountId>(),
871 Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount
872 ),
873 "Account ID {} is not a NEAR deterministic account",
874 invalid_account_id
875 );
876 }
877 }
878
879 #[test]
880 #[cfg(feature = "arbitrary")]
881 fn test_arbitrary() {
882 let corpus = [
883 ("a|bcd", None),
884 ("ab|cde", Some("ab")),
885 ("a_-b", None),
886 ("ab_-c", Some("ab")),
887 ("a", None),
888 ("miraclx.near", Some("miraclx.near")),
889 (
890 "01234567890123456789012345678901234567890123456789012345678901234",
891 None,
892 ),
893 ];
894
895 for (input, expected_output) in corpus {
896 assert!(input.len() <= u8::MAX as usize);
897 let data = [input.as_bytes(), &[input.len() as _]].concat();
898 let mut u = arbitrary::Unstructured::new(&data);
899
900 assert_eq!(
901 u.arbitrary::<&AccountIdRef>()
902 .ok()
903 .map(AsRef::<str>::as_ref),
904 expected_output
905 );
906 }
907 }
908}