Skip to content

Commit

Permalink
AccountType, add EthImplicitAccount (#14)
Browse files Browse the repository at this point in the history
Part of near/nearcore#10018.

This PR introduces some changes from
near/nearcore#9969 laying groundwork for real
protocol changes to be done in a separate PR.

Summary:
- Add `AccountType` enum: `NamedAccount`, `NearImplicitAccount`, or
`EthImplicitAccount`.
- Parse 40 characters long hexadecimal addresses prefixed with `'0x'` as
`EthImplicitAccount`.
  • Loading branch information
staffik committed Nov 3, 2023
1 parent 724adbe commit 2efa2d9
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 20 deletions.
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.
/// 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

0 comments on commit 2efa2d9

Please sign in to comment.