Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AccountType, add EthImplicitAccount #14

Merged
merged 3 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 104 additions & 19 deletions src/account_id_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,32 @@ use crate::{AccountId, ParseAccountError};
#[cfg_attr(feature = "abi", derive(schemars::JsonSchema, BorshSchema))]
pub struct AccountIdRef(pub(crate) str);

/// Enum representing possible types of accounts.
frol marked this conversation as resolved.
Show resolved Hide resolved
/// This `enum` is returned by the [`get_account_type`] method on [`AccountIdRef`].
/// See its documentation for more.
///
/// [`get_account_type`]: AccountIdRef::get_account_type
/// [`AccountIdRef`]: struct.AccountIdRef.html
#[derive(PartialEq)]
pub enum AccountType {
/// Any valid account, that is neither NEAR-implicit nor ETH-implicit.
NamedAccount,
/// An account with 64 characters long hexadecimal address.
NearImplicitAccount,
/// An account which address starts with '0x', followed by 40 hex characters.
EthImplicitAccount,
}

impl AccountType {
pub fn is_implicit(&self) -> bool {
match &self {
Self::NearImplicitAccount => true,
Self::EthImplicitAccount => true,
Self::NamedAccount => false,
}
}
}

impl AccountIdRef {
/// Shortest valid length for a NEAR Account ID.
pub const MIN_LEN: usize = crate::validation::MIN_LEN;
Expand Down Expand Up @@ -140,29 +166,38 @@ impl AccountIdRef {
.map_or(false, |s| !s.contains('.'))
}

/// Returns `true` if the `AccountId` is a 64 characters long hexadecimal.
/// Returns `AccountType::EthImplicitAccount` if the `AccountId` is a 40 characters long hexadecimal prefixed with '0x'.
/// Returns `AccountType::NearImplicitAccount` if the `AccountId` is a 64 characters long hexadecimal.
/// Otherwise, returns `AccountType::NamedAccount`.
///
/// See [Implicit-Accounts](https://docs.near.org/docs/concepts/account#implicit-accounts).
///
/// ## Examples
///
/// ```
/// use near_account_id::AccountId;
/// use near_account_id::{AccountId, AccountType};
///
/// let alice: AccountId = "alice.near".parse().unwrap();
/// assert!(!alice.is_implicit());
/// assert!(alice.get_account_type() == AccountType::NamedAccount);
///
/// let rando = "98793cd91a3f870fb126f66285808c7e094afcfc4eda8a970f6648cdf0dbd6de"
/// let eth_rando = "0xb794f5ea0ba39494ce839613fffba74279579268"
/// .parse::<AccountId>()
/// .unwrap();
/// assert!(rando.is_implicit());
/// assert!(eth_rando.get_account_type() == AccountType::EthImplicitAccount);
///
/// let near_rando = "98793cd91a3f870fb126f66285808c7e094afcfc4eda8a970f6648cdf0dbd6de"
/// .parse::<AccountId>()
/// .unwrap();
/// assert!(near_rando.get_account_type() == AccountType::NearImplicitAccount);
/// ```
pub fn is_implicit(&self) -> bool {
self.0.len() == 64
&& self
.as_bytes()
.iter()
.all(|b| matches!(b, b'a'..=b'f' | b'0'..=b'9'))
pub fn get_account_type(&self) -> AccountType {
if crate::validation::is_eth_implicit(self.as_str()) {
return AccountType::EthImplicitAccount;
}
if crate::validation::is_near_implicit(self.as_str()) {
return AccountType::NearImplicitAccount;
}
AccountType::NamedAccount
}

/// Returns `true` if this `AccountId` is the system account.
Expand Down Expand Up @@ -457,6 +492,9 @@ mod tests {
"alex-skidanov",
"b-o_w_e-n",
"no_lols",
// ETH-implicit account
"0xb794f5ea0ba39494ce839613fffba74279579268",
// NEAR-implicit account
"0123456789012345678901234567890123456789012345678901234567890123",
];
for account_id in ok_top_level_account_ids {
Expand Down Expand Up @@ -581,6 +619,11 @@ mod tests {
"123456789012345678901234567890123456789012345678901234567890",
"1234567890.123456789012345678901234567890123456789012345678901234567890",
),
(
"b794f5ea0ba39494ce839613fffba74279579268",
// ETH-implicit account
"0xb794f5ea0ba39494ce839613fffba74279579268",
),
("aa", "ъ@aa"),
("aa", "ъ.aa"),
];
Expand All @@ -598,40 +641,82 @@ mod tests {
}

#[test]
fn test_is_account_id_64_len_hex() {
let valid_64_len_hex_account_ids = &[
fn test_is_account_id_near_implicit() {
let valid_near_implicit_account_ids = &[
"0000000000000000000000000000000000000000000000000000000000000000",
"6174617461746174617461746174617461746174617461746174617461746174",
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"20782e20662e64666420482123494b6b6c677573646b6c66676a646b6c736667",
];
for valid_account_id in valid_64_len_hex_account_ids {
for valid_account_id in valid_near_implicit_account_ids {
assert!(
matches!(
AccountIdRef::new(valid_account_id),
Ok(account_id) if account_id.is_implicit()
Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount
),
"Account ID {} should be valid 64-len hex",
valid_account_id
);
}

let invalid_64_len_hex_account_ids = &[
let invalid_near_implicit_account_ids = &[
"000000000000000000000000000000000000000000000000000000000000000",
"6.74617461746174617461746174617461746174617461746174617461746174",
"012-456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"fffff_ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
"00000000000000000000000000000000000000000000000000000000000000",
];
for invalid_account_id in invalid_64_len_hex_account_ids {
for invalid_account_id in invalid_near_implicit_account_ids {
assert!(
!matches!(
AccountIdRef::new(invalid_account_id),
Ok(account_id) if account_id.is_implicit()
Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount
),
"Account ID {} is not a NEAR-implicit account",
invalid_account_id
);
}
}

#[test]
fn test_is_account_id_eth_implicit() {
let valid_eth_implicit_account_ids = &[
"0x0000000000000000000000000000000000000000",
"0x6174617461746174617461746174617461746174",
"0x0123456789abcdef0123456789abcdef01234567",
"0xffffffffffffffffffffffffffffffffffffffff",
"0x20782e20662e64666420482123494b6b6c677573",
];
for valid_account_id in valid_eth_implicit_account_ids {
assert!(
matches!(
valid_account_id.parse::<AccountId>(),
Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount
),
"Account ID {} should be valid 42-len hex, starting with 0x",
valid_account_id
);
}

let invalid_eth_implicit_account_ids = &[
"04b794f5ea0ba39494ce839613fffba74279579268",
"0x000000000000000000000000000000000000000",
"0x6.74617461746174617461746174617461746174",
"0x012-456789abcdef0123456789abcdef01234567",
"0xfffff_ffffffffffffffffffffffffffffffffff",
"0xoooooooooooooooooooooooooooooooooooooooo",
"0x00000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
];
for invalid_account_id in invalid_eth_implicit_account_ids {
assert!(
!matches!(
invalid_account_id.parse::<AccountId>(),
Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount
),
"Account ID {} is not an implicit account",
"Account ID {} is not an ETH-implicit account",
invalid_account_id
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ mod test_data;
mod validation;

pub use account_id::AccountId;
pub use account_id_ref::AccountIdRef;
pub use account_id_ref::{AccountIdRef, AccountType};
pub use errors::{ParseAccountError, ParseErrorKind};
14 changes: 14 additions & 0 deletions src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ pub fn validate(account_id: &str) -> Result<(), ParseAccountError> {
}
}

pub fn is_eth_implicit(account_id: &str) -> bool {
account_id.len() == 42
&& account_id.starts_with("0x")
&& account_id[2..].as_bytes().iter().all(|b| matches!(b, b'a'..=b'f' | b'0'..=b'9'))
}

pub fn is_near_implicit(account_id: &str) -> bool {
account_id.len() == 64
&& account_id
.as_bytes()
.iter()
.all(|b| matches!(b, b'a'..=b'f' | b'0'..=b'9'))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading