From 4f33fac2fff8386d309000459ab7cdd434917c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Fri, 27 Oct 2023 16:34:21 +0200 Subject: [PATCH 01/10] refactor: eth-implicit accounts --- core/account-id/src/lib.rs | 842 ++++++++++++++++++ core/crypto/src/errors.rs | 4 +- core/crypto/src/util.rs | 16 +- core/primitives-core/src/runtime/fees.rs | 32 +- core/primitives/src/test_utils.rs | 26 +- core/primitives/src/utils.rs | 22 + .../access_key_nonce_for_implicit_accounts.rs | 31 +- .../tests/client/features/delegate_action.rs | 46 +- .../src/tests/standard_cases/mod.rs | 49 +- .../src/tests/standard_cases/runtime.rs | 10 +- runtime/near-vm-runner/src/logic/logic.rs | 9 +- runtime/runtime/src/actions.rs | 61 +- runtime/runtime/src/config.rs | 10 +- tools/fork-network/src/cli.rs | 14 +- tools/mirror/src/genesis.rs | 14 +- tools/mirror/src/key_mapping.rs | 24 +- tools/mirror/src/lib.rs | 10 +- 17 files changed, 1077 insertions(+), 143 deletions(-) create mode 100644 core/account-id/src/lib.rs diff --git a/core/account-id/src/lib.rs b/core/account-id/src/lib.rs new file mode 100644 index 00000000000..457188e15b1 --- /dev/null +++ b/core/account-id/src/lib.rs @@ -0,0 +1,842 @@ +//! This crate provides a type for representing a syntactically valid, unique account identifier on the [NEAR](https://near.org) network. +//! +//! ## Account ID Rules +//! +//! - Minimum length is `2` +//! - Maximum length is `64` +//! - An **Account ID** consists of **Account ID parts** separated by `.`, example: +//! - `root` ✔ +//! - `alice.near` ✔ +//! - `app.stage.testnet` ✔ +//! - Must not start or end with separators (`_`, `-` or `.`): +//! - `_alice.` ✗ +//! - `.bob.near-` ✗ +//! - Each part of the **Account ID** consists of lowercase alphanumeric symbols separated either by `_` or `-`, example: +//! - `ƒelicia.near` ✗ (`ƒ` is not `f`) +//! - `1_4m_n0t-al1c3.near` ✔ +//! - Separators are not permitted to immediately follow each other, example: +//! - `alice..near` ✗ +//! - `not-_alice.near` ✗ +//! - An **Account ID** that is 64 characters long and consists of lowercase hex characters is a specific **implicit account ID** +//! +//! Learn more here: +//! +//! Also see [Error kind precedence](AccountId#error-kind-precedence). +//! +//! ## Usage +//! +//! ``` +//! use near_account_id::AccountId; +//! +//! let alice: AccountId = "alice.near".parse().unwrap(); +//! +//! assert!("ƒelicia.near".parse::().is_err()); // (ƒ is not f) +//! ``` + +use std::{fmt, str::FromStr}; + +mod errors; + +#[cfg(feature = "borsh")] +mod borsh; +#[cfg(feature = "serde")] +mod serde; + +pub use errors::{ParseAccountError, ParseErrorKind}; + +/// NEAR Account Identifier. +/// +/// This is a unique, syntactically valid, human-readable account identifier on the NEAR network. +/// +/// [See the crate-level docs for information about validation.](index.html#account-id-rules) +/// +/// Also see [Error kind precedence](AccountId#error-kind-precedence). +/// +/// ## Examples +/// +/// ``` +/// use near_account_id::AccountId; +/// +/// let alice: AccountId = "alice.near".parse().unwrap(); +/// +/// assert!("ƒelicia.near".parse::().is_err()); // (ƒ is not f) +/// ``` +#[derive(Eq, Ord, Hash, Clone, Debug, PartialEq, PartialOrd)] +pub struct AccountId(Box); + +#[derive(PartialEq)] +pub enum AccountType { + NamedAccount, + NearImplicitAccount, + EthImplicitAccount, +} + +impl AccountType { + pub fn is_implicit(&self) -> bool { + match &self { + Self::NearImplicitAccount => true, + // TODO change to true later, see https://github.com/near/nearcore/issues/10018 + Self::EthImplicitAccount => false, + Self::NamedAccount => false, + } + } +} + +impl AccountId { + /// Shortest valid length for a NEAR Account ID. + pub const MIN_LEN: usize = 2; + /// Longest valid length for a NEAR Account ID. + pub const MAX_LEN: usize = 64; + + /// Returns a string slice of the entire Account ID. + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::AccountId; + /// + /// let carol: AccountId = "carol.near".parse().unwrap(); + /// assert_eq!("carol.near", carol.as_str()); + /// ``` + pub fn as_str(&self) -> &str { + self + } + + /// Returns `true` if the `AccountId` is a top-level NEAR Account ID. + /// + /// See [Top-level Accounts](https://docs.near.org/docs/concepts/account#top-level-accounts). + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::AccountId; + /// + /// let near_tla: AccountId = "near".parse().unwrap(); + /// assert!(near_tla.is_top_level()); + /// + /// // "alice.near" is a sub account of "near" account + /// let alice: AccountId = "alice.near".parse().unwrap(); + /// assert!(!alice.is_top_level()); + /// ``` + pub fn is_top_level(&self) -> bool { + !self.is_system() && !self.contains('.') + } + + /// Returns `true` if the `AccountId` is a direct sub-account of the provided parent account. + /// + /// See [Subaccounts](https://docs.near.org/docs/concepts/account#subaccounts). + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::AccountId; + /// + /// let near_tla: AccountId = "near".parse().unwrap(); + /// assert!(near_tla.is_top_level()); + /// + /// let alice: AccountId = "alice.near".parse().unwrap(); + /// assert!(alice.is_sub_account_of(&near_tla)); + /// + /// let alice_app: AccountId = "app.alice.near".parse().unwrap(); + /// + /// // While app.alice.near is a sub account of alice.near, + /// // app.alice.near is not a sub account of near + /// assert!(alice_app.is_sub_account_of(&alice)); + /// assert!(!alice_app.is_sub_account_of(&near_tla)); + /// ``` + pub fn is_sub_account_of(&self, parent: &AccountId) -> bool { + self.strip_suffix(parent.as_str()) + .and_then(|s| s.strip_suffix('.')) + .map_or(false, |s| !s.contains('.')) + } + + /// Returns `true` if the `AccountId` is a 40 characters long hexadecimal prefixed with '0x'. + /// + /// See [Implicit-Accounts](https://docs.near.org/docs/concepts/account#implicit-accounts). + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::AccountId; + /// + /// let alice: AccountId = "alice.near".parse().unwrap(); + /// assert!(!alice.is_eth_implicit()); + /// + /// let rando = "0xb794f5ea0ba39494ce839613fffba74279579268" + /// .parse::() + /// .unwrap(); + /// assert!(rando.is_eth_implicit()); + /// ``` + fn is_eth_implicit(&self) -> bool { + self.len() == 42 + && self.starts_with("0x") + && self[2..].as_bytes().iter().all(|b| matches!(b, b'a'..=b'f' | b'0'..=b'9')) + } + + /// Returns `true` if the `AccountId` is a 64 characters long hexadecimal. + /// + /// See [Implicit-Accounts](https://docs.near.org/docs/concepts/account#implicit-accounts). + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::AccountId; + /// + /// let alice: AccountId = "alice.near".parse().unwrap(); + /// assert!(!alice.is_near_implicit()); + /// + /// let rando = "98793cd91a3f870fb126f66285808c7e094afcfc4eda8a970f6648cdf0dbd6de" + /// .parse::() + /// .unwrap(); + /// assert!(rando.is_near_implicit()); + /// ``` + fn is_near_implicit(&self) -> bool { + self.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 self.is_eth_implicit() { + return AccountType::EthImplicitAccount; + } + if self.is_near_implicit() { + return AccountType::NearImplicitAccount; + } + AccountType::NamedAccount + } + + /// Returns `true` if this `AccountId` is the system account. + /// + /// See [System account](https://nomicon.io/DataStructures/Account.html?highlight=system#system-account). + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::AccountId; + /// + /// let alice: AccountId = "alice.near".parse().unwrap(); + /// assert!(!alice.is_system()); + /// + /// let system: AccountId = "system".parse().unwrap(); + /// assert!(system.is_system()); + /// ``` + pub fn is_system(&self) -> bool { + self.as_str() == "system" + } + + /// Validates a string as a well-structured NEAR Account ID. + /// + /// Checks Account ID validity without constructing an `AccountId` instance. + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::{AccountId, ParseErrorKind}; + /// + /// assert!(AccountId::validate("alice.near").is_ok()); + /// + /// assert!( + /// matches!( + /// AccountId::validate("ƒelicia.near"), // fancy ƒ! + /// Err(err) if err.kind() == &ParseErrorKind::InvalidChar + /// ) + /// ); + /// ``` + /// + /// ## Error kind precedence + /// + /// If an Account ID has multiple format violations, the first one would be reported. + /// + /// ### Examples + /// + /// ``` + /// use near_account_id::{AccountId, ParseErrorKind}; + /// + /// assert!( + /// matches!( + /// AccountId::validate("A__ƒƒluent."), + /// Err(err) if err.kind() == &ParseErrorKind::InvalidChar + /// ) + /// ); + /// + /// assert!( + /// matches!( + /// AccountId::validate("a__ƒƒluent."), + /// Err(err) if err.kind() == &ParseErrorKind::RedundantSeparator + /// ) + /// ); + /// + /// assert!( + /// matches!( + /// AccountId::validate("aƒƒluent."), + /// Err(err) if err.kind() == &ParseErrorKind::InvalidChar + /// ) + /// ); + /// + /// assert!( + /// matches!( + /// AccountId::validate("affluent."), + /// Err(err) if err.kind() == &ParseErrorKind::RedundantSeparator + /// ) + /// ); + /// ``` + pub fn validate(account_id: &str) -> Result<(), ParseAccountError> { + if account_id.len() < AccountId::MIN_LEN { + Err(ParseAccountError { kind: ParseErrorKind::TooShort, char: None }) + } else if account_id.len() > AccountId::MAX_LEN { + Err(ParseAccountError { kind: ParseErrorKind::TooLong, char: None }) + } else { + // Adapted from https://github.com/near/near-sdk-rs/blob/fd7d4f82d0dfd15f824a1cf110e552e940ea9073/near-sdk/src/environment/env.rs#L819 + + // NOTE: We don't want to use Regex here, because it requires extra time to compile it. + // The valid account ID regex is /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/ + // Instead the implementation is based on the previous character checks. + + // We can safely assume that last char was a separator. + let mut last_char_is_separator = true; + + let mut this = None; + for (i, c) in account_id.chars().enumerate() { + this.replace((i, c)); + let current_char_is_separator = match c { + 'a'..='z' | '0'..='9' => false, + '-' | '_' | '.' => true, + _ => { + return Err(ParseAccountError { + kind: ParseErrorKind::InvalidChar, + char: this, + }); + } + }; + if current_char_is_separator && last_char_is_separator { + return Err(ParseAccountError { + kind: ParseErrorKind::RedundantSeparator, + char: this, + }); + } + last_char_is_separator = current_char_is_separator; + } + + if last_char_is_separator { + return Err(ParseAccountError { + kind: ParseErrorKind::RedundantSeparator, + char: this, + }); + } + Ok(()) + } + } + + /// Creates an `AccountId` without any validation checks. + /// + /// Please note that this is restrictively for internal use only. Plus, being behind a feature flag, + /// this could be removed later in the future. + /// + /// ## Safety + /// + /// Since this skips validation and constructs an `AccountId` regardless, + /// the caller bears the responsibility of ensuring that the Account ID is valid. + /// You can use the [`AccountId::validate`] function sometime after its creation but before it's use. + /// + /// ## Examples + /// + /// ``` + /// use near_account_id::AccountId; + /// + /// let alice = AccountId::new_unvalidated("alice.near".to_string()); + /// assert!(AccountId::validate(alice.as_str()).is_ok()); + /// + /// let ƒelicia = AccountId::new_unvalidated("ƒelicia.near".to_string()); + /// assert!(AccountId::validate(ƒelicia.as_str()).is_err()); + /// ``` + #[doc(hidden)] + #[cfg(feature = "internal_unstable")] + #[deprecated = "AccountId construction without validation is illegal since #4440"] + pub fn new_unvalidated(account_id: String) -> Self { + Self(account_id.into_boxed_str()) + } +} + +impl std::ops::Deref for AccountId { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl AsRef for AccountId { + fn as_ref(&self) -> &str { + self + } +} + +impl std::borrow::Borrow for AccountId { + fn borrow(&self) -> &str { + self + } +} + +impl FromStr for AccountId { + type Err = ParseAccountError; + + fn from_str(account_id: &str) -> Result { + Self::validate(account_id)?; + Ok(Self(account_id.into())) + } +} + +impl TryFrom> for AccountId { + type Error = ParseAccountError; + + fn try_from(account_id: Box) -> Result { + Self::validate(&account_id)?; + Ok(Self(account_id)) + } +} + +impl TryFrom for AccountId { + type Error = ParseAccountError; + + fn try_from(account_id: String) -> Result { + Self::validate(&account_id)?; + Ok(Self(account_id.into_boxed_str())) + } +} + +impl fmt::Display for AccountId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl From for String { + fn from(account_id: AccountId) -> Self { + account_id.0.into_string() + } +} + +impl From for Box { + fn from(value: AccountId) -> Box { + value.0 + } +} + +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for AccountId { + fn size_hint(_depth: usize) -> (usize, Option) { + (AccountId::MIN_LEN, Some(AccountId::MAX_LEN)) + } + + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let mut s = u.arbitrary::<&str>()?; + loop { + match s.parse::() { + Ok(account_id) => break Ok(account_id), + Err(ParseAccountError { char: Some((idx, _)), .. }) => { + s = &s[..idx]; + continue; + } + _ => break Err(arbitrary::Error::IncorrectFormat), + } + } + } + + fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result { + <&str as arbitrary::Arbitrary>::arbitrary_take_rest(u)? + .parse() + .map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub const OK_ACCOUNT_IDS: [&str; 25] = [ + "aa", + "a-a", + "a-aa", + "100", + "0o", + "com", + "near", + "bowen", + "b-o_w_e-n", + "b.owen", + "bro.wen", + "a.ha", + "a.b-a.ra", + "system", + "over.9000", + "google.com", + "illia.cheapaccounts.near", + "0o0ooo00oo00o", + "alex-skidanov", + "10-4.8-2", + "b-o_w_e-n", + "no_lols", + "0123456789012345678901234567890123456789012345678901234567890123", + "0xb794f5ea0ba39494ce839613fffba74279579268", + // Valid, but can't be created + "near.a", + ]; + + pub const BAD_ACCOUNT_IDS: [&str; 24] = [ + "a", + "A", + "Abc", + "-near", + "near-", + "-near-", + "near.", + ".near", + "near@", + "@near", + "неар", + "@@@@@", + "0__0", + "0_-_0", + "0_-_0", + "..", + "a..near", + "nEar", + "_bowen", + "hello world", + "abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz", + "01234567890123456789012345678901234567890123456789012345678901234", + // `@` separators are banned now + "some-complex-address@gmail.com", + "sub.buy_d1gitz@atata@b0-rg.c_0_m", + ]; + + #[test] + fn test_is_valid_account_id() { + for account_id in OK_ACCOUNT_IDS.iter().cloned() { + if let Err(err) = AccountId::validate(account_id) { + panic!("Valid account id {:?} marked invalid: {}", account_id, err.kind()); + } + } + + for account_id in BAD_ACCOUNT_IDS.iter().cloned() { + if AccountId::validate(account_id).is_ok() { + panic!("Invalid account id {:?} marked valid", account_id); + } + } + } + + #[test] + fn test_err_kind_classification() { + let id = "ErinMoriarty.near".parse::(); + debug_assert!( + matches!( + id, + Err(ParseAccountError { kind: ParseErrorKind::InvalidChar, char: Some((0, 'E')) }) + ), + "{:?}", + id + ); + + let id = "-KarlUrban.near".parse::(); + debug_assert!( + matches!( + id, + Err(ParseAccountError { + kind: ParseErrorKind::RedundantSeparator, + char: Some((0, '-')) + }) + ), + "{:?}", + id + ); + + let id = "anthonystarr.".parse::(); + debug_assert!( + matches!( + id, + Err(ParseAccountError { + kind: ParseErrorKind::RedundantSeparator, + char: Some((12, '.')) + }) + ), + "{:?}", + id + ); + + let id = "jack__Quaid.near".parse::(); + debug_assert!( + matches!( + id, + Err(ParseAccountError { + kind: ParseErrorKind::RedundantSeparator, + char: Some((5, '_')) + }) + ), + "{:?}", + id + ); + } + + #[test] + fn test_is_valid_top_level_account_id() { + let ok_top_level_account_ids = &[ + "aa", + "a-a", + "a-aa", + "100", + "0o", + "com", + "near", + "bowen", + "b-o_w_e-n", + "0o0ooo00oo00o", + "alex-skidanov", + "b-o_w_e-n", + "no_lols", + "0xb794f5ea0ba39494ce839613fffba74279579268", + "0123456789012345678901234567890123456789012345678901234567890123", + ]; + for account_id in ok_top_level_account_ids { + assert!( + account_id + .parse::() + .map_or(false, |account_id| account_id.is_top_level()), + "Valid top level account id {:?} marked invalid", + account_id + ); + } + + let bad_top_level_account_ids = &[ + "ƒelicia.near", // fancy ƒ! + "near.a", + "b.owen", + "bro.wen", + "a.ha", + "a.b-a.ra", + "some-complex-address@gmail.com", + "sub.buy_d1gitz@atata@b0-rg.c_0_m", + "over.9000", + "google.com", + "illia.cheapaccounts.near", + "10-4.8-2", + "a", + "A", + "Abc", + "-near", + "near-", + "-near-", + "near.", + ".near", + "near@", + "@near", + "неар", + "@@@@@", + "0__0", + "0_-_0", + "0_-_0", + "..", + "a..near", + "nEar", + "_bowen", + "hello world", + "abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz", + "01234567890123456789012345678901234567890123456789012345678901234", + // Valid regex and length, but reserved + "system", + ]; + for account_id in bad_top_level_account_ids { + assert!( + !account_id + .parse::() + .map_or(false, |account_id| account_id.is_top_level()), + "Invalid top level account id {:?} marked valid", + account_id + ); + } + } + + #[test] + fn test_is_valid_sub_account_id() { + let ok_pairs = &[ + ("test", "a.test"), + ("test-me", "abc.test-me"), + ("gmail.com", "abc.gmail.com"), + ("gmail.com", "abc-lol.gmail.com"), + ("gmail.com", "abc_lol.gmail.com"), + ("gmail.com", "bro-abc_lol.gmail.com"), + ("g0", "0g.g0"), + ("1g", "1g.1g"), + ("5-3", "4_2.5-3"), + ]; + for (signer_id, sub_account_id) in ok_pairs { + assert!( + matches!( + (signer_id.parse::(), sub_account_id.parse::()), + (Ok(signer_id), Ok(sub_account_id)) if sub_account_id.is_sub_account_of(&signer_id) + ), + "Failed to create sub-account {:?} by account {:?}", + sub_account_id, + signer_id + ); + } + + let bad_pairs = &[ + ("test", ".test"), + ("test", "test"), + ("test", "a1.a.test"), + ("test", "est"), + ("test", ""), + ("test", "st"), + ("test5", "ббб"), + ("test", "a-test"), + ("test", "etest"), + ("test", "a.etest"), + ("test", "retest"), + ("test-me", "abc-.test-me"), + ("test-me", "Abc.test-me"), + ("test-me", "-abc.test-me"), + ("test-me", "a--c.test-me"), + ("test-me", "a_-c.test-me"), + ("test-me", "a-_c.test-me"), + ("test-me", "_abc.test-me"), + ("test-me", "abc_.test-me"), + ("test-me", "..test-me"), + ("test-me", "a..test-me"), + ("gmail.com", "a.abc@gmail.com"), + ("gmail.com", ".abc@gmail.com"), + ("gmail.com", ".abc@gmail@com"), + ("gmail.com", "abc@gmail@com"), + ("test", "a@test"), + ("test_me", "abc@test_me"), + ("gmail.com", "abc@gmail.com"), + ("gmail@com", "abc.gmail@com"), + ("gmail.com", "abc-lol@gmail.com"), + ("gmail@com", "abc_lol.gmail@com"), + ("gmail@com", "bro-abc_lol.gmail@com"), + ("gmail.com", "123456789012345678901234567890123456789012345678901234567890@gmail.com"), + ( + "123456789012345678901234567890123456789012345678901234567890", + "1234567890.123456789012345678901234567890123456789012345678901234567890", + ), + ( + "b794f5ea0ba39494ce839613fffba74279579268", + "0xb794f5ea0ba39494ce839613fffba74279579268", + ), + ("aa", "ъ@aa"), + ("aa", "ъ.aa"), + ]; + for (signer_id, sub_account_id) in bad_pairs { + assert!( + !matches!( + (signer_id.parse::(), sub_account_id.parse::()), + (Ok(signer_id), Ok(sub_account_id)) if sub_account_id.is_sub_account_of(&signer_id) + ), + "Invalid sub-account {:?} created by account {:?}", + sub_account_id, + signer_id + ); + } + } + + #[test] + fn test_is_account_id_near_implicit() { + let valid_near_implicit_account_ids = &[ + "0000000000000000000000000000000000000000000000000000000000000000", + "6174617461746174617461746174617461746174617461746174617461746174", + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "20782e20662e64666420482123494b6b6c677573646b6c66676a646b6c736667", + ]; + for valid_account_id in valid_near_implicit_account_ids { + assert!( + matches!( + valid_account_id.parse::(), + Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount + ), + "Account ID {} should be valid 64-len hex", + valid_account_id + ); + } + + let invalid_near_implicit_account_ids = &[ + "000000000000000000000000000000000000000000000000000000000000000", + "6.74617461746174617461746174617461746174617461746174617461746174", + "012-456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + "fffff_ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", + "00000000000000000000000000000000000000000000000000000000000000", + ]; + for invalid_account_id in invalid_near_implicit_account_ids { + assert!( + !matches!( + invalid_account_id.parse::(), + Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount + ), + "Account ID {} is not an 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::(), + 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", + ]; + for invalid_account_id in invalid_eth_implicit_account_ids { + assert!( + !matches!( + invalid_account_id.parse::(), + Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount + ), + "Account ID {} is not an implicit account", + invalid_account_id + ); + } + } + + #[test] + #[cfg(feature = "arbitrary")] + fn test_arbitrary() { + let corpus = [ + ("a|bcd", None), + ("ab|cde", Some("ab")), + ("a_-b", None), + ("ab_-c", Some("ab")), + ("a", None), + ("miraclx.near", Some("miraclx.near")), + ("01234567890123456789012345678901234567890123456789012345678901234", None), + ]; + + for (input, expected_output) in corpus { + assert!(input.len() <= u8::MAX as usize); + let data = [input.as_bytes(), &[input.len() as _]].concat(); + let mut u = arbitrary::Unstructured::new(&data); + + assert_eq!(u.arbitrary::().as_deref().ok(), expected_output); + } + } +} diff --git a/core/crypto/src/errors.rs b/core/crypto/src/errors.rs index 22031f33ad7..0fb96860b06 100644 --- a/core/crypto/src/errors.rs +++ b/core/crypto/src/errors.rs @@ -48,6 +48,6 @@ impl From for ParseSignatureError { #[derive(Debug, Clone, thiserror::Error)] pub enum ImplicitPublicKeyError { - #[error("'{account_id}' is not an implicit account")] - AccountIsNotImplicit { account_id: AccountId }, + #[error("'{account_id}' is not a NEAR-implicit account")] + AccountIsNotNearImplicit { account_id: AccountId }, } diff --git a/core/crypto/src/util.rs b/core/crypto/src/util.rs index d0205c6802f..431964dec15 100644 --- a/core/crypto/src/util.rs +++ b/core/crypto/src/util.rs @@ -7,6 +7,8 @@ use curve25519_dalek::traits::VartimeMultiscalarMul; pub use curve25519_dalek::ristretto::RistrettoPoint as Point; pub use curve25519_dalek::scalar::Scalar; +use near_account_id::AccountType; + pub fn vmul2(s1: Scalar, p1: &Point, s2: Scalar, p2: &Point) -> Point { Point::vartime_multiscalar_mul(&[s1, s2], [p1, p2].iter().copied()) } @@ -96,16 +98,16 @@ impl< } impl PublicKey { - /// Create the implicit public key from an implicit account ID. + /// Create the implicit public key from an NEAR-implicit account ID. /// - /// Returns `ImplicitPublicKeyError::AccountIsNotImplicit` if the given - /// account id is not a valid implicit account ID. - /// See [`near_account_id::AccountId#is_implicit`] for the definition. - pub fn from_implicit_account( + /// Returns `ImplicitPublicKeyError::AccountIsNotNearImplicit` if the given + /// account id is not a valid NEAR-implicit account ID. + /// See [`near_account_id::AccountId#is_near_implicit`] for the definition. + pub fn from_near_implicit_account( account_id: &near_account_id::AccountId, ) -> Result { - if !account_id.is_implicit() { - return Err(ImplicitPublicKeyError::AccountIsNotImplicit { + if account_id.get_account_type() != AccountType::NearImplicitAccount { + return Err(ImplicitPublicKeyError::AccountIsNotNearImplicit { account_id: account_id.clone(), }); } diff --git a/core/primitives-core/src/runtime/fees.rs b/core/primitives-core/src/runtime/fees.rs index ee268e5ff73..aa5e245107a 100644 --- a/core/primitives-core/src/runtime/fees.rs +++ b/core/primitives-core/src/runtime/fees.rs @@ -7,6 +7,7 @@ use crate::config::ActionCosts; use crate::num_rational::Rational32; use crate::types::{Balance, Gas}; use enum_map::EnumMap; +use near_account_id::AccountType; /// Costs associated with an object that can only be sent over the network (and executed /// by the receiver). @@ -203,28 +204,35 @@ impl StorageUsageConfig { /// Helper functions for computing Transfer fees. /// In case of implicit account creation they always include extra fees for the CreateAccount and -/// AddFullAccessKey actions that are implicit. +/// AddFullAccessKey (for NEAR-implicit account only) actions that are implicit. /// We can assume that no overflow will happen here. -pub fn transfer_exec_fee(cfg: &RuntimeFeesConfig, is_receiver_implicit: bool) -> Gas { +pub fn transfer_exec_fee( + cfg: &RuntimeFeesConfig, + is_receiver_implicit: bool, + receiver_account_type: AccountType, +) -> Gas { + let mut result = cfg.fee(ActionCosts::transfer).exec_fee(); if is_receiver_implicit { - cfg.fee(ActionCosts::create_account).exec_fee() - + cfg.fee(ActionCosts::add_full_access_key).exec_fee() - + cfg.fee(ActionCosts::transfer).exec_fee() - } else { - cfg.fee(ActionCosts::transfer).exec_fee() + result += cfg.fee(ActionCosts::create_account).exec_fee(); + if receiver_account_type == AccountType::NearImplicitAccount { + result += cfg.fee(ActionCosts::add_full_access_key).exec_fee(); + } } + result } pub fn transfer_send_fee( cfg: &RuntimeFeesConfig, sender_is_receiver: bool, is_receiver_implicit: bool, + receiver_account_type: AccountType, ) -> Gas { + let mut result = cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver); if is_receiver_implicit { - cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver) - + cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver) - + cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) - } else { - cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) + result += cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver); + if receiver_account_type == AccountType::NearImplicitAccount { + result += cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver); + } } + result } diff --git a/core/primitives/src/test_utils.rs b/core/primitives/src/test_utils.rs index bb35526869b..327dc1c1524 100644 --- a/core/primitives/src/test_utils.rs +++ b/core/primitives/src/test_utils.rs @@ -555,24 +555,36 @@ pub fn create_test_signer(account_name: &str) -> InMemoryValidatorSigner { /// /// Should be used only in tests. pub fn create_user_test_signer(account_name: &AccountIdRef) -> InMemorySigner { - let account_id: AccountId = account_name.to_owned(); - if account_id == implicit_test_account() { - InMemorySigner::from_secret_key(account_id, implicit_test_account_secret()) + let account_id = account_name.to_owned(); + if account_id == near_implicit_test_account() { + InMemorySigner::from_secret_key(account_id, near_implicit_test_account_secret()) + } else if account_id == eth_implicit_test_account() { + InMemorySigner::from_secret_key(account_id, eth_implicit_test_account_secret()) } else { InMemorySigner::from_seed(account_id, KeyType::ED25519, account_name.as_str()) } } -/// A fixed implicit account for which tests can know the private key. -pub fn implicit_test_account() -> AccountId { +/// A fixed NEAR-implicit account for which tests can know the private key. +pub fn near_implicit_test_account() -> AccountId { "061b1dd17603213b00e1a1e53ba060ad427cef4887bd34a5e0ef09010af23b0a".parse().unwrap() } -/// Private key for the fixed implicit test account. -pub fn implicit_test_account_secret() -> SecretKey { +/// Private key for the fixed NEAR-implicit test account. +pub fn near_implicit_test_account_secret() -> SecretKey { "ed25519:5roj6k68kvZu3UEJFyXSfjdKGrodgZUfFLZFpzYXWtESNsLWhYrq3JGi4YpqeVKuw1m9R2TEHjfgWT1fjUqB1DNy".parse().unwrap() } +/// A fixed ETH-implicit account for which tests can know the private key. +pub fn eth_implicit_test_account() -> AccountId { + "0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap() +} + +/// Private key for the fixed ETH-implicit test account. +pub fn eth_implicit_test_account_secret() -> SecretKey { + "secp256k1:X4ETFKtQkSGVoZEnkn7bZ3LyajJaK2b3eweXaKmynGx".parse().unwrap() +} + impl FinalExecutionOutcomeView { #[track_caller] /// Check transaction and all transitive receipts for success status. diff --git a/core/primitives/src/utils.rs b/core/primitives/src/utils.rs index 749320f7e33..3032b19ea33 100644 --- a/core/primitives/src/utils.rs +++ b/core/primitives/src/utils.rs @@ -16,6 +16,10 @@ use crate::version::{ ProtocolVersion, CORRECT_RANDOM_VALUE_PROTOCOL_VERSION, CREATE_HASH_PROTOCOL_VERSION, CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION, }; + +use near_crypto::{KeyType, PublicKey}; +use near_primitives_core::account::id::AccountId; + use std::mem::size_of; use std::ops::Deref; @@ -465,10 +469,28 @@ where Serializable(object) } +/// Derives `AccountId` from `PublicKey``. +/// If the key type is ED25519, returns hex-encoded copy of the key. +pub fn derive_account_id_from_public_key(public_key: &PublicKey) -> AccountId { + match public_key.key_type() { + KeyType::ED25519 => { + hex::encode(public_key.key_data()).parse().unwrap() + }, + _ => unimplemented!(), + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn test_derive_account_id_from_ed25519_public_key() { + let public_key = PublicKey::from_seed(KeyType::ED25519, "test"); + let expected: AccountId = "bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap(); + assert_eq!(derive_account_id_from_public_key(&public_key), expected); + } + #[test] fn test_num_chunk_producers() { for num_seats in 1..50 { diff --git a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs index 2e2db28d05e..77dad36b981 100644 --- a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs +++ b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs @@ -7,7 +7,7 @@ use near_chain_configs::Genesis; use near_chunks::metrics::PARTIAL_ENCODED_CHUNK_FORWARD_CACHED_WITHOUT_HEADER; use near_client::test_utils::{create_chunk_with_transactions, TestEnv}; use near_client::ProcessTxResponse; -use near_crypto::{InMemorySigner, KeyType, Signer}; +use near_crypto::{InMemorySigner, KeyType, SecretKey, Signer}; use near_network::shards_manager::ShardsManagerRequestFromNetwork; use near_network::types::{NetworkRequests, PeerManagerMessageRequest}; use near_o11y::testonly::init_test_logger; @@ -18,6 +18,7 @@ use near_primitives::shard_layout::ShardLayout; use near_primitives::sharding::ChunkHash; use near_primitives::transaction::SignedTransaction; use near_primitives::types::{AccountId, BlockHeight}; +use near_primitives::utils::derive_account_id_from_public_key; use near_primitives::version::{ProtocolFeature, ProtocolVersion}; use near_primitives::views::FinalExecutionStatus; use nearcore::config::GenesisExt; @@ -118,6 +119,7 @@ fn test_transaction_hash_collision() { /// should fail since the protocol upgrade. fn get_status_of_tx_hash_collision_for_implicit_account( protocol_version: ProtocolVersion, + implicit_account_signer: InMemorySigner, ) -> ProcessTxResponse { let epoch_length = 100; let mut genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1); @@ -128,17 +130,11 @@ fn get_status_of_tx_hash_collision_for_implicit_account( .nightshade_runtimes(&genesis) .build(); let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap(); - - let signer1 = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); - - let public_key = &signer1.public_key; - let raw_public_key = public_key.unwrap_as_ed25519().0.to_vec(); - let implicit_account_id = AccountId::try_from(hex::encode(&raw_public_key)).unwrap(); - let implicit_account_signer = - InMemorySigner::from_secret_key(implicit_account_id.clone(), signer1.secret_key.clone()); let deposit_for_account_creation = 10u128.pow(23); let mut height = 1; let blocks_number = 5; + let signer1 = InMemorySigner::from_seed("test1".parse().unwrap(), KeyType::ED25519, "test1"); + let implicit_account_id = implicit_account_signer.account_id.clone(); // Send money to implicit account, invoking its creation. let send_money_tx = SignedTransaction::send_money( @@ -202,23 +198,28 @@ fn get_status_of_tx_hash_collision_for_implicit_account( response } -/// Test that duplicate transactions from implicit accounts are properly rejected. +/// Test that duplicate transactions from NEAR-implicit accounts are properly rejected. #[test] fn test_transaction_hash_collision_for_implicit_account_fail() { let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version(); + let secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); + let implicit_account_id = derive_account_id_from_public_key(&secret_key.public_key()); + let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( - get_status_of_tx_hash_collision_for_implicit_account(protocol_version), + get_status_of_tx_hash_collision_for_implicit_account(protocol_version, implicit_account_signer), ProcessTxResponse::InvalidTx(InvalidTxError::InvalidNonce { .. }) ); } -/// Test that duplicate transactions from implicit accounts are not rejected until protocol upgrade. +/// Test that duplicate transactions from NEAR-implicit accounts are not rejected until protocol upgrade. #[test] fn test_transaction_hash_collision_for_implicit_account_ok() { - let protocol_version = - ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1; + let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1; + let secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); + let implicit_account_id = derive_account_id_from_public_key(&secret_key.public_key()); + let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( - get_status_of_tx_hash_collision_for_implicit_account(protocol_version), + get_status_of_tx_hash_collision_for_implicit_account(protocol_version, implicit_account_signer), ProcessTxResponse::ValidTx ); } diff --git a/integration-tests/src/tests/client/features/delegate_action.rs b/integration-tests/src/tests/client/features/delegate_action.rs index 1aa2c11aa53..38693b1ddab 100644 --- a/integration-tests/src/tests/client/features/delegate_action.rs +++ b/integration-tests/src/tests/client/features/delegate_action.rs @@ -9,13 +9,13 @@ use near_chain::ChainGenesis; use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; use near_crypto::{KeyType, PublicKey, Signer}; -use near_primitives::account::{AccessKey, AccessKeyPermission, FunctionCallPermission}; +use near_primitives::account::{AccessKey, AccessKeyPermission, FunctionCallPermission, id::AccountType}; use near_primitives::config::ActionCosts; use near_primitives::errors::{ ActionError, ActionErrorKind, ActionsValidationError, InvalidAccessKeyError, InvalidTxError, TxExecutionError, }; -use near_primitives::test_utils::{create_user_test_signer, implicit_test_account}; +use near_primitives::test_utils::{create_user_test_signer, near_implicit_test_account}; use near_primitives::transaction::{ Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction, DeployContractAction, FunctionCallAction, StakeAction, TransferAction, @@ -133,10 +133,10 @@ fn check_meta_tx_execution( .get_access_key(&relayer, &PublicKey::from_seed(KeyType::ED25519, relayer.as_ref())) .unwrap() .nonce; - let user_pubk = if sender.is_implicit() { - PublicKey::from_implicit_account(&sender).unwrap() - } else { - PublicKey::from_seed(KeyType::ED25519, sender.as_ref()) + let user_pubk = match sender.get_account_type() { + AccountType::NearImplicitAccount => PublicKey::from_near_implicit_account(&sender).unwrap(), + AccountType::EthImplicitAccount => PublicKey::from_seed(KeyType::SECP256K1, sender.as_ref()), + AccountType::NamedAccount => PublicKey::from_seed(KeyType::ED25519, sender.as_ref()), }; let user_nonce_before = node_user.get_access_key(&sender, &user_pubk).unwrap().nonce; @@ -779,11 +779,9 @@ fn meta_tx_create_named_account() { /// Try creating an implicit account with `CreateAction` which is not allowed in /// or outside meta transactions and must fail with `OnlyImplicitAccountCreationAllowed`. -#[test] -fn meta_tx_create_implicit_account_fails() { +fn meta_tx_create_implicit_account_fails(new_account: AccountId) { let relayer = bob_account(); let sender = alice_account(); - let new_account: AccountId = implicit_test_account(); let node = RuntimeNode::new(&relayer); let actions = vec![Action::CreateAccount(CreateAccountAction {})]; @@ -798,17 +796,20 @@ fn meta_tx_create_implicit_account_fails() { )); } +#[test] +fn meta_tx_create_near_implicit_account_fails() { + meta_tx_create_implicit_account_fails(near_implicit_test_account()); +} + /// Try creating an implicit account with a meta tx transfer and use the account /// in the same meta transaction. /// /// This is expected to fail with `AccountDoesNotExist`, known limitation of NEP-366. /// It only works with accounts that already exists because it needs to do a /// nonce check against the access key, which can only exist if the account exists. -#[test] -fn meta_tx_create_and_use_implicit_account() { +fn meta_tx_create_and_use_implicit_account(new_account: AccountId) { let relayer = bob_account(); let sender = alice_account(); - let new_account: AccountId = implicit_test_account(); let node = RuntimeNode::new(&relayer); // Check the account doesn't exist, yet. We will attempt creating it. @@ -832,17 +833,20 @@ fn meta_tx_create_and_use_implicit_account() { )); } +#[test] +fn meta_tx_create_and_use_near_implicit_account() { + meta_tx_create_and_use_implicit_account(near_implicit_test_account()); +} + /// Creating an implicit account with a meta tx transfer and use the account in /// a second meta transaction. /// /// Creation through a meta tx should work as normal, it's just that the relayer /// pays for the storage and the user could delete the account and cash in, /// hence this workflow is not ideal from all circumstances. -#[test] -fn meta_tx_create_implicit_account() { +fn meta_tx_create_implicit_account(new_account: AccountId) { let relayer = bob_account(); let sender = alice_account(); - let new_account: AccountId = implicit_test_account(); let node = RuntimeNode::new(&relayer); // Check account doesn't exist, yet @@ -851,7 +855,12 @@ fn meta_tx_create_implicit_account() { let fee_helper = fee_helper(&node); let initial_amount = nearcore::NEAR_BASE; let actions = vec![Action::Transfer(TransferAction { deposit: initial_amount })]; - let tx_cost = fee_helper.create_account_transfer_full_key_cost(); + + let tx_cost = match new_account.get_account_type() { + AccountType::NearImplicitAccount => fee_helper.create_account_transfer_full_key_cost(), + AccountType::EthImplicitAccount => panic!("must be implicit"), + AccountType::NamedAccount => panic!("must be implicit"), + }; check_meta_tx_no_fn_call( &node, actions, @@ -887,3 +896,8 @@ fn meta_tx_create_implicit_account() { let balance = node.view_balance(&new_account).expect("failed looking up balance"); assert_eq!(balance, initial_amount); } + +#[test] +fn meta_tx_create_near_implicit_account() { + meta_tx_create_implicit_account(near_implicit_test_account()); +} diff --git a/integration-tests/src/tests/standard_cases/mod.rs b/integration-tests/src/tests/standard_cases/mod.rs index 2fe939a0bcb..878a90dd7ad 100644 --- a/integration-tests/src/tests/standard_cases/mod.rs +++ b/integration-tests/src/tests/standard_cases/mod.rs @@ -4,9 +4,9 @@ mod rpc; mod runtime; use assert_matches::assert_matches; -use near_crypto::{InMemorySigner, KeyType}; +use near_crypto::{InMemorySigner, KeyType, PublicKey}; use near_jsonrpc_primitives::errors::ServerError; -use near_primitives::account::{AccessKey, AccessKeyPermission, FunctionCallPermission}; +use near_primitives::account::{AccessKey, AccessKeyPermission, FunctionCallPermission, id::AccountType}; use near_primitives::config::{ActionCosts, ExtCosts}; use near_primitives::errors::{ ActionError, ActionErrorKind, FunctionCallError, InvalidAccessKeyError, InvalidTxError, @@ -14,6 +14,7 @@ use near_primitives::errors::{ }; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::types::{AccountId, Balance, TrieNodesCount}; +use near_primitives::utils::derive_account_id_from_public_key; use near_primitives::views::{ AccessKeyView, AccountView, ExecutionMetadataView, FinalExecutionOutcomeView, FinalExecutionStatus, @@ -327,16 +328,20 @@ pub fn test_send_money(node: impl Node) { ); } -pub fn transfer_tokens_implicit_account(node: impl Node) { +pub fn transfer_tokens_implicit_account(node: impl Node, public_key: PublicKey) { let account_id = &node.account_id().unwrap(); let node_user = node.user(); let root = node_user.get_state_root(); let tokens_used = 10u128.pow(25); let fee_helper = fee_helper(&node); - let transfer_cost = fee_helper.transfer_cost_64len_hex(); - let public_key = node_user.signer().public_key(); - let raw_public_key = public_key.unwrap_as_ed25519().0.to_vec(); - let receiver_id = AccountId::try_from(hex::encode(&raw_public_key)).unwrap(); + let receiver_id = derive_account_id_from_public_key(&public_key); + + let transfer_cost = match receiver_id.get_account_type() { + AccountType::NearImplicitAccount => fee_helper.create_account_transfer_full_key_cost(), + AccountType::EthImplicitAccount => std::panic!("must be implicit"), + AccountType::NamedAccount => std::panic!("must be implicit"), + }; + let transaction_result = node_user.send_money(account_id.clone(), receiver_id.clone(), tokens_used).unwrap(); assert_eq!(transaction_result.status, FinalExecutionStatus::SuccessValue(Vec::new())); @@ -357,8 +362,14 @@ pub fn transfer_tokens_implicit_account(node: impl Node) { let AccountView { amount, locked, .. } = node_user.view_account(&receiver_id).unwrap(); assert_eq!((amount, locked), (tokens_used, 0)); - let view_access_key = node_user.get_access_key(&receiver_id, &public_key).unwrap(); - assert_eq!(view_access_key, AccessKey::full_access().into()); + let view_access_key = node_user.get_access_key(&receiver_id, &public_key); + match receiver_id.get_account_type() { + AccountType::NearImplicitAccount => { + assert_eq!(view_access_key.unwrap(), AccessKey::full_access().into()); + }, + AccountType::EthImplicitAccount => std::panic!("must be implicit"), + AccountType::NamedAccount => std::panic!("must be implicit"), + } let transaction_result = node_user.send_money(account_id.clone(), receiver_id.clone(), tokens_used).unwrap(); @@ -382,16 +393,13 @@ pub fn transfer_tokens_implicit_account(node: impl Node) { assert_eq!((amount, locked), (tokens_used * 2, 0)); } -pub fn trying_to_create_implicit_account(node: impl Node) { +pub fn trying_to_create_implicit_account(node: impl Node, public_key: PublicKey) { let account_id = &node.account_id().unwrap(); let node_user = node.user(); let root = node_user.get_state_root(); let tokens_used = 10u128.pow(25); let fee_helper = fee_helper(&node); - - let public_key = node_user.signer().public_key(); - let raw_public_key = public_key.unwrap_as_ed25519().0.to_vec(); - let receiver_id = AccountId::try_from(hex::encode(&raw_public_key)).unwrap(); + let receiver_id = derive_account_id_from_public_key(&public_key); let transaction_result = node_user .create_account( @@ -402,14 +410,19 @@ pub fn trying_to_create_implicit_account(node: impl Node) { ) .unwrap(); - let cost = fee_helper.create_account_transfer_full_key_cost_fail_on_create_account() - + fee_helper.gas_to_balance( - fee_helper.cfg().fee(ActionCosts::create_account).send_fee(false) + let cost = match receiver_id.get_account_type() { + AccountType::NearImplicitAccount => + fee_helper.create_account_transfer_full_key_cost_fail_on_create_account() + + fee_helper.gas_to_balance( + fee_helper.cfg().fee(ActionCosts::create_account).send_fee(false) + fee_helper .cfg() .fee(near_primitives::config::ActionCosts::add_full_access_key) .send_fee(false), - ); + ), + AccountType::EthImplicitAccount => std::panic!("must be implicit"), + AccountType::NamedAccount => std::panic!("must be implicit"), + }; assert_eq!( transaction_result.status, diff --git a/integration-tests/src/tests/standard_cases/runtime.rs b/integration-tests/src/tests/standard_cases/runtime.rs index 68060498834..c30551085f0 100644 --- a/integration-tests/src/tests/standard_cases/runtime.rs +++ b/integration-tests/src/tests/standard_cases/runtime.rs @@ -114,15 +114,17 @@ fn test_send_money_runtime() { } #[test] -fn test_transfer_tokens_implicit_account_runtime() { +fn test_transfer_tokens_near_implicit_account_runtime() { let node = create_runtime_node(); - transfer_tokens_implicit_account(node); + let public_key = node.user().signer().public_key(); + transfer_tokens_implicit_account(node, public_key); } #[test] -fn test_trying_to_create_implicit_account_runtime() { +fn test_trying_to_create_near_implicit_account_runtime() { let node = create_runtime_node(); - trying_to_create_implicit_account(node); + let public_key = node.user().signer().public_key(); + trying_to_create_implicit_account(node, public_key); } #[test] diff --git a/runtime/near-vm-runner/src/logic/logic.rs b/runtime/near-vm-runner/src/logic/logic.rs index 9f9ce4a6348..6d52e1ff30e 100644 --- a/runtime/near-vm-runner/src/logic/logic.rs +++ b/runtime/near-vm-runner/src/logic/logic.rs @@ -1772,10 +1772,11 @@ impl<'a> VMLogic<'a> { let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; let receiver_id = self.ext.get_receipt_receiver(receipt_idx); - let is_receiver_implicit = - self.config.implicit_account_creation && receiver_id.is_implicit(); - let send_fee = transfer_send_fee(self.fees_config, sir, is_receiver_implicit); - let exec_fee = transfer_exec_fee(self.fees_config, is_receiver_implicit); + let is_receiver_implicit = self.config.implicit_account_creation && receiver_id.get_account_type().is_implicit(); + let send_fee = + transfer_send_fee(self.fees_config, sir, is_receiver_implicit, receiver_id.get_account_type()); + let exec_fee = + transfer_exec_fee(self.fees_config, is_receiver_implicit, receiver_id.get_account_type()); let burn_gas = send_fee; let use_gas = burn_gas.checked_add(exec_fee).ok_or(HostError::IntegerOverflow)?; self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::transfer)?; diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index f5751ad32e3..eab5907d172 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -26,6 +26,7 @@ use near_primitives::utils::create_random_seed; use near_primitives::version::{ ProtocolFeature, ProtocolVersion, DELETE_KEY_STORAGE_USAGE_PROTOCOL_VERSION, }; +use near_primitives_core::account::id::AccountType; use near_primitives_core::config::ActionCosts; use near_store::{ get_access_key, get_code, remove_access_key, remove_account, set_access_key, set_code, @@ -440,30 +441,36 @@ pub(crate) fn action_implicit_account_creation_transfer( ) { *actor_id = account_id.clone(); - let mut access_key = AccessKey::full_access(); - // Set default nonce for newly created access key to avoid transaction hash collision. - // See . - if checked_feature!("stable", AccessKeyNonceForImplicitAccounts, current_protocol_version) { - access_key.nonce = (block_height - 1) - * near_primitives::account::AccessKey::ACCESS_KEY_NONCE_RANGE_MULTIPLIER; - } - - // Invariant: The account_id is hex like (implicit account id). - // It holds because in the only calling site, we've checked the permissions before. - // unwrap: Can only fail if `account_id` is not implicit. - let public_key = PublicKey::from_implicit_account(account_id).unwrap(); - - *account = Some(Account::new( - transfer.deposit, - 0, - CryptoHash::default(), - fee_config.storage_usage_config.num_bytes_account - + public_key.len() as u64 - + borsh::object_length(&access_key).unwrap() as u64 - + fee_config.storage_usage_config.num_extra_bytes_record, - )); + match account_id.get_account_type() { + AccountType::NearImplicitAccount => { + let mut access_key = AccessKey::full_access(); + // Set default nonce for newly created access key to avoid transaction hash collision. + // See . + if checked_feature!("stable", AccessKeyNonceForImplicitAccounts, current_protocol_version) { + access_key.nonce = (block_height - 1) + * near_primitives::account::AccessKey::ACCESS_KEY_NONCE_RANGE_MULTIPLIER; + } - set_access_key(state_update, account_id.clone(), public_key, &access_key); + // Invariant: The account_id is hex like (implicit account id). + // It holds because in the only calling site, we've checked the permissions before. + // unwrap: Can only fail if `account_id` is not NEAR-implicit. + let public_key = PublicKey::from_near_implicit_account(account_id).unwrap(); + + *account = Some(Account::new( + transfer.deposit, + 0, + CryptoHash::default(), + fee_config.storage_usage_config.num_bytes_account + + public_key.len() as u64 + + borsh::object_length(&access_key).unwrap() as u64 + + fee_config.storage_usage_config.num_extra_bytes_record, + )); + + set_access_key(state_update, account_id.clone(), public_key, &access_key); + }, + AccountType::EthImplicitAccount => panic!("must be implicit"), + AccountType::NamedAccount => panic!("must be implicit"), + } } pub(crate) fn action_deploy_contract( @@ -883,14 +890,14 @@ pub(crate) fn check_account_existence( .into()); } else { // TODO: this should be `config.implicit_account_creation`. - if config.wasm_config.implicit_account_creation && account_id.is_implicit() { - // If the account doesn't exist and it's 64-length hex account ID, then you + if config.wasm_config.implicit_account_creation && account_id.get_account_type().is_implicit() { + // If the account doesn't exist and it's implicit, then you // should only be able to create it using single transfer action. // Because you should not be able to add another access key to the account in // the same transaction. // Otherwise you can hijack an account without having the private key for the // public key. We've decided to make it an invalid transaction to have any other - // actions on the 64-length hex accounts. + // actions on the implicit hex accounts. // The easiest way is to reject the `CreateAccount` action. // See https://github.com/nearprotocol/NEPs/pull/71 return Err(ActionErrorKind::OnlyImplicitAccountCreationAllowed { @@ -904,7 +911,7 @@ pub(crate) fn check_account_existence( if account.is_none() { return if config.wasm_config.implicit_account_creation && is_the_only_action - && account_id.is_implicit() + && account_id.get_account_type().is_implicit() && !is_refund { // OK. It's implicit account creation. diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index cf6f6cc2e99..f932a2377e3 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -96,9 +96,10 @@ pub fn total_send_fees( } Transfer(_) => { // Account for implicit account creation + let receiver_account_type = receiver_id.get_account_type(); let is_receiver_implicit = - config.wasm_config.implicit_account_creation && receiver_id.is_implicit(); - transfer_send_fee(fees, sender_is_receiver, is_receiver_implicit) + config.wasm_config.implicit_account_creation && receiver_account_type.is_implicit(); + transfer_send_fee(fees, sender_is_receiver, is_receiver_implicit, receiver_account_type) } Stake(_) => fees.fee(ActionCosts::stake).send_fee(sender_is_receiver), AddKey(add_key_action) => match &add_key_action.access_key.permission { @@ -188,9 +189,10 @@ pub fn exec_fee(config: &RuntimeConfig, action: &Action, receiver_id: &AccountId } Transfer(_) => { // Account for implicit account creation + let receiver_account_type = receiver_id.get_account_type(); let is_receiver_implicit = - config.wasm_config.implicit_account_creation && receiver_id.is_implicit(); - transfer_exec_fee(fees, is_receiver_implicit) + config.wasm_config.implicit_account_creation && receiver_account_type.is_implicit(); + transfer_exec_fee(fees, is_receiver_implicit, receiver_account_type) } Stake(_) => fees.fee(ActionCosts::stake).exec_fee(), AddKey(add_key_action) => match &add_key_action.access_key.permission { diff --git a/tools/fork-network/src/cli.rs b/tools/fork-network/src/cli.rs index 27a766d464f..a55ac733618 100644 --- a/tools/fork-network/src/cli.rs +++ b/tools/fork-network/src/cli.rs @@ -486,7 +486,7 @@ impl ForkNetworkCommand { if let Some(sr) = StateRecord::from_raw_key_value(key.clone(), value.clone()) { match sr { StateRecord::AccessKey { account_id, public_key, access_key } => { - if !account_id.is_implicit() + if !account_id.get_account_type().is_implicit() && access_key.permission == AccessKeyPermission::FullAccess { has_full_key.insert(account_id.clone()); @@ -503,7 +503,7 @@ impl ForkNetworkCommand { } StateRecord::Account { account_id, account } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { let new_account_id = map_account(&account_id, None); storage_mutator.delete_account(account_id)?; storage_mutator.set_account(new_account_id, account)?; @@ -511,7 +511,7 @@ impl ForkNetworkCommand { } } StateRecord::Data { account_id, data_key, value } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { let new_account_id = map_account(&account_id, None); storage_mutator.delete_data(account_id, &data_key)?; storage_mutator.set_data(new_account_id, &data_key, value)?; @@ -519,7 +519,7 @@ impl ForkNetworkCommand { } } StateRecord::Contract { account_id, code } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { let new_account_id = map_account(&account_id, None); storage_mutator.delete_code(account_id)?; storage_mutator.set_code(new_account_id, code)?; @@ -527,7 +527,7 @@ impl ForkNetworkCommand { } } StateRecord::PostponedReceipt(receipt) => { - if receipt.predecessor_id.is_implicit() || receipt.receiver_id.is_implicit() + if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() { let new_receipt = Receipt { predecessor_id: map_account(&receipt.predecessor_id, None), @@ -541,7 +541,7 @@ impl ForkNetworkCommand { } } StateRecord::ReceivedData { account_id, data_id, data } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { let new_account_id = map_account(&account_id, None); storage_mutator.delete_received_data(account_id, data_id)?; storage_mutator.set_received_data(new_account_id, data_id, &data)?; @@ -549,7 +549,7 @@ impl ForkNetworkCommand { } } StateRecord::DelayedReceipt(receipt) => { - if receipt.predecessor_id.is_implicit() || receipt.receiver_id.is_implicit() + if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() { let new_receipt = Receipt { predecessor_id: map_account(&receipt.predecessor_id, None), diff --git a/tools/mirror/src/genesis.rs b/tools/mirror/src/genesis.rs index 892b4a9eee0..932c608d718 100644 --- a/tools/mirror/src/genesis.rs +++ b/tools/mirror/src/genesis.rs @@ -38,7 +38,7 @@ pub fn map_records>( public_key: replacement.public_key(), access_key: access_key.clone(), }; - if !account_id.is_implicit() + if !account_id.get_account_type().is_implicit() && access_key.permission == AccessKeyPermission::FullAccess { has_full_key.insert(account_id.clone()); @@ -48,7 +48,7 @@ pub fn map_records>( records_seq.serialize_element(&new_record).unwrap(); } StateRecord::Account { account_id, .. } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } else { accounts.insert(account_id.clone()); @@ -56,19 +56,19 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::Data { account_id, .. } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } records_seq.serialize_element(&r).unwrap(); } StateRecord::Contract { account_id, .. } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } records_seq.serialize_element(&r).unwrap(); } StateRecord::PostponedReceipt(receipt) => { - if receipt.predecessor_id.is_implicit() || receipt.receiver_id.is_implicit() { + if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() { receipt.predecessor_id = crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref()); receipt.receiver_id = @@ -77,13 +77,13 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::ReceivedData { account_id, .. } => { - if account_id.is_implicit() { + if account_id.get_account_type().is_implicit() { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } records_seq.serialize_element(&r).unwrap(); } StateRecord::DelayedReceipt(receipt) => { - if receipt.predecessor_id.is_implicit() || receipt.receiver_id.is_implicit() { + if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() { receipt.predecessor_id = crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref()); receipt.receiver_id = diff --git a/tools/mirror/src/key_mapping.rs b/tools/mirror/src/key_mapping.rs index 7c5b2d1989c..92c7ee85817 100644 --- a/tools/mirror/src/key_mapping.rs +++ b/tools/mirror/src/key_mapping.rs @@ -1,6 +1,8 @@ use hkdf::Hkdf; use near_crypto::{ED25519PublicKey, ED25519SecretKey, PublicKey, Secp256K1PublicKey, SecretKey}; +use near_primitives_core::account::id::AccountType; use near_primitives::types::AccountId; +use near_primitives::utils::derive_account_id_from_public_key; use sha2::Sha256; // there is nothing special about this key, it's just some randomly generated one. @@ -90,18 +92,22 @@ pub fn map_key(key: &PublicKey, secret: Option<&[u8; crate::secret::SECRET_LEN]> } } -// If it's an implicit account, interprets it as an ed25519 public key, maps that and then returns -// the resulting implicit account. Otherwise does nothing. We do this so that transactions creating -// an implicit account by sending money will generate an account that we can control +// If it's a NEAR-implicit account, interprets it as an ed25519 public key, +// maps that and then returns the resulting implicit account. Otherwise does nothing. +// We do this so that transactions creating an implicit account +// by sending money will generate an account that we can control. pub fn map_account( account_id: &AccountId, secret: Option<&[u8; crate::secret::SECRET_LEN]>, ) -> AccountId { - if account_id.is_implicit() { - let public_key = PublicKey::from_implicit_account(account_id).expect("must be implicit"); - let mapped_key = map_key(&public_key, secret); - hex::encode(mapped_key.public_key().key_data()).parse().unwrap() - } else { - account_id.clone() + match account_id.get_account_type() { + AccountType::NearImplicitAccount => { + let public_key = + PublicKey::from_near_implicit_account(account_id).expect("must be implicit"); + let mapped_key = map_key(&public_key, secret); + derive_account_id_from_public_key(&mapped_key.public_key()) + }, + AccountType::EthImplicitAccount => account_id.clone(), + AccountType::NamedAccount => account_id.clone() } } diff --git a/tools/mirror/src/lib.rs b/tools/mirror/src/lib.rs index 97ad65edb64..55094c32ef9 100644 --- a/tools/mirror/src/lib.rs +++ b/tools/mirror/src/lib.rs @@ -26,6 +26,7 @@ use near_primitives::views::{ ExecutionOutcomeWithIdView, ExecutionStatusView, QueryRequest, QueryResponseKind, SignedTransactionView, }; +use near_primitives_core::account::id::AccountType; use near_primitives_core::account::{AccessKey, AccessKeyPermission}; use near_primitives_core::types::{Nonce, ShardId}; use nearcore::config::NearConfig; @@ -989,7 +990,7 @@ impl TxMirror { actions.push(Action::DeleteKey(Box::new(DeleteKeyAction { public_key }))); } Action::Transfer(_) => { - if tx.receiver_id().is_implicit() && source_actions.len() == 1 { + if tx.receiver_id().get_account_type().is_implicit() && source_actions.len() == 1 { let target_account = crate::key_mapping::map_account(tx.receiver_id(), self.secret.as_ref()); if !account_exists(&self.target_view_client, &target_account) @@ -1001,9 +1002,10 @@ impl TxMirror { ) })? { - let public_key = PublicKey::from_implicit_account(&target_account) - .expect("must be implicit"); - nonce_updates.insert((target_account, public_key)); + if target_account.get_account_type() == AccountType::NearImplicitAccount { + let public_key = PublicKey::from_near_implicit_account(&target_account).expect("must be implicit"); + nonce_updates.insert((target_account, public_key)); + } } } actions.push(action.clone()); From f4b490a975166ae6777db42f6db54d68f1ee14ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Mon, 30 Oct 2023 14:56:18 +0100 Subject: [PATCH 02/10] Cargo fmt check style fixes --- core/primitives/src/utils.rs | 7 +++--- .../access_key_nonce_for_implicit_accounts.rs | 13 ++++++++--- .../tests/client/features/delegate_action.rs | 4 +++- .../src/tests/standard_cases/mod.rs | 23 +++++++++++-------- runtime/near-vm-runner/src/logic/logic.rs | 18 +++++++++++---- runtime/runtime/src/actions.rs | 12 +++++++--- runtime/runtime/src/config.rs | 11 ++++++--- tools/fork-network/src/cli.rs | 6 +++-- tools/mirror/src/genesis.rs | 8 +++++-- tools/mirror/src/key_mapping.rs | 6 ++--- tools/mirror/src/lib.rs | 11 ++++++--- 11 files changed, 80 insertions(+), 39 deletions(-) diff --git a/core/primitives/src/utils.rs b/core/primitives/src/utils.rs index 3032b19ea33..50655b36e80 100644 --- a/core/primitives/src/utils.rs +++ b/core/primitives/src/utils.rs @@ -473,9 +473,7 @@ where /// If the key type is ED25519, returns hex-encoded copy of the key. pub fn derive_account_id_from_public_key(public_key: &PublicKey) -> AccountId { match public_key.key_type() { - KeyType::ED25519 => { - hex::encode(public_key.key_data()).parse().unwrap() - }, + KeyType::ED25519 => hex::encode(public_key.key_data()).parse().unwrap(), _ => unimplemented!(), } } @@ -487,7 +485,8 @@ mod tests { #[test] fn test_derive_account_id_from_ed25519_public_key() { let public_key = PublicKey::from_seed(KeyType::ED25519, "test"); - let expected: AccountId = "bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap(); + let expected: AccountId = + "bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap(); assert_eq!(derive_account_id_from_public_key(&public_key), expected); } diff --git a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs index 77dad36b981..7d8acccd62e 100644 --- a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs +++ b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs @@ -206,7 +206,10 @@ fn test_transaction_hash_collision_for_implicit_account_fail() { let implicit_account_id = derive_account_id_from_public_key(&secret_key.public_key()); let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( - get_status_of_tx_hash_collision_for_implicit_account(protocol_version, implicit_account_signer), + get_status_of_tx_hash_collision_for_implicit_account( + protocol_version, + implicit_account_signer + ), ProcessTxResponse::InvalidTx(InvalidTxError::InvalidNonce { .. }) ); } @@ -214,12 +217,16 @@ fn test_transaction_hash_collision_for_implicit_account_fail() { /// Test that duplicate transactions from NEAR-implicit accounts are not rejected until protocol upgrade. #[test] fn test_transaction_hash_collision_for_implicit_account_ok() { - let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1; + let protocol_version = + ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1; let secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); let implicit_account_id = derive_account_id_from_public_key(&secret_key.public_key()); let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( - get_status_of_tx_hash_collision_for_implicit_account(protocol_version, implicit_account_signer), + get_status_of_tx_hash_collision_for_implicit_account( + protocol_version, + implicit_account_signer + ), ProcessTxResponse::ValidTx ); } diff --git a/integration-tests/src/tests/client/features/delegate_action.rs b/integration-tests/src/tests/client/features/delegate_action.rs index 38693b1ddab..1b234b7121c 100644 --- a/integration-tests/src/tests/client/features/delegate_action.rs +++ b/integration-tests/src/tests/client/features/delegate_action.rs @@ -9,7 +9,9 @@ use near_chain::ChainGenesis; use near_chain_configs::Genesis; use near_client::test_utils::TestEnv; use near_crypto::{KeyType, PublicKey, Signer}; -use near_primitives::account::{AccessKey, AccessKeyPermission, FunctionCallPermission, id::AccountType}; +use near_primitives::account::{ + id::AccountType, AccessKey, AccessKeyPermission, FunctionCallPermission, +}; use near_primitives::config::ActionCosts; use near_primitives::errors::{ ActionError, ActionErrorKind, ActionsValidationError, InvalidAccessKeyError, InvalidTxError, diff --git a/integration-tests/src/tests/standard_cases/mod.rs b/integration-tests/src/tests/standard_cases/mod.rs index 878a90dd7ad..c1b8b663285 100644 --- a/integration-tests/src/tests/standard_cases/mod.rs +++ b/integration-tests/src/tests/standard_cases/mod.rs @@ -6,7 +6,9 @@ mod runtime; use assert_matches::assert_matches; use near_crypto::{InMemorySigner, KeyType, PublicKey}; use near_jsonrpc_primitives::errors::ServerError; -use near_primitives::account::{AccessKey, AccessKeyPermission, FunctionCallPermission, id::AccountType}; +use near_primitives::account::{ + id::AccountType, AccessKey, AccessKeyPermission, FunctionCallPermission, +}; use near_primitives::config::{ActionCosts, ExtCosts}; use near_primitives::errors::{ ActionError, ActionErrorKind, FunctionCallError, InvalidAccessKeyError, InvalidTxError, @@ -366,7 +368,7 @@ pub fn transfer_tokens_implicit_account(node: impl Node, public_key: PublicKey) match receiver_id.get_account_type() { AccountType::NearImplicitAccount => { assert_eq!(view_access_key.unwrap(), AccessKey::full_access().into()); - }, + } AccountType::EthImplicitAccount => std::panic!("must be implicit"), AccountType::NamedAccount => std::panic!("must be implicit"), } @@ -411,15 +413,16 @@ pub fn trying_to_create_implicit_account(node: impl Node, public_key: PublicKey) .unwrap(); let cost = match receiver_id.get_account_type() { - AccountType::NearImplicitAccount => + AccountType::NearImplicitAccount => { fee_helper.create_account_transfer_full_key_cost_fail_on_create_account() - + fee_helper.gas_to_balance( - fee_helper.cfg().fee(ActionCosts::create_account).send_fee(false) - + fee_helper - .cfg() - .fee(near_primitives::config::ActionCosts::add_full_access_key) - .send_fee(false), - ), + + fee_helper.gas_to_balance( + fee_helper.cfg().fee(ActionCosts::create_account).send_fee(false) + + fee_helper + .cfg() + .fee(near_primitives::config::ActionCosts::add_full_access_key) + .send_fee(false), + ) + } AccountType::EthImplicitAccount => std::panic!("must be implicit"), AccountType::NamedAccount => std::panic!("must be implicit"), }; diff --git a/runtime/near-vm-runner/src/logic/logic.rs b/runtime/near-vm-runner/src/logic/logic.rs index 6d52e1ff30e..9179a9ed05b 100644 --- a/runtime/near-vm-runner/src/logic/logic.rs +++ b/runtime/near-vm-runner/src/logic/logic.rs @@ -1772,11 +1772,19 @@ impl<'a> VMLogic<'a> { let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; let receiver_id = self.ext.get_receipt_receiver(receipt_idx); - let is_receiver_implicit = self.config.implicit_account_creation && receiver_id.get_account_type().is_implicit(); - let send_fee = - transfer_send_fee(self.fees_config, sir, is_receiver_implicit, receiver_id.get_account_type()); - let exec_fee = - transfer_exec_fee(self.fees_config, is_receiver_implicit, receiver_id.get_account_type()); + let is_receiver_implicit = + self.config.implicit_account_creation && receiver_id.get_account_type().is_implicit(); + let send_fee = transfer_send_fee( + self.fees_config, + sir, + is_receiver_implicit, + receiver_id.get_account_type(), + ); + let exec_fee = transfer_exec_fee( + self.fees_config, + is_receiver_implicit, + receiver_id.get_account_type(), + ); let burn_gas = send_fee; let use_gas = burn_gas.checked_add(exec_fee).ok_or(HostError::IntegerOverflow)?; self.gas_counter.pay_action_accumulated(burn_gas, use_gas, ActionCosts::transfer)?; diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index eab5907d172..89c699ca8f0 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -446,7 +446,11 @@ pub(crate) fn action_implicit_account_creation_transfer( let mut access_key = AccessKey::full_access(); // Set default nonce for newly created access key to avoid transaction hash collision. // See . - if checked_feature!("stable", AccessKeyNonceForImplicitAccounts, current_protocol_version) { + if checked_feature!( + "stable", + AccessKeyNonceForImplicitAccounts, + current_protocol_version + ) { access_key.nonce = (block_height - 1) * near_primitives::account::AccessKey::ACCESS_KEY_NONCE_RANGE_MULTIPLIER; } @@ -467,7 +471,7 @@ pub(crate) fn action_implicit_account_creation_transfer( )); set_access_key(state_update, account_id.clone(), public_key, &access_key); - }, + } AccountType::EthImplicitAccount => panic!("must be implicit"), AccountType::NamedAccount => panic!("must be implicit"), } @@ -890,7 +894,9 @@ pub(crate) fn check_account_existence( .into()); } else { // TODO: this should be `config.implicit_account_creation`. - if config.wasm_config.implicit_account_creation && account_id.get_account_type().is_implicit() { + if config.wasm_config.implicit_account_creation + && account_id.get_account_type().is_implicit() + { // If the account doesn't exist and it's implicit, then you // should only be able to create it using single transfer action. // Because you should not be able to add another access key to the account in diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index f932a2377e3..e5abb47e9c4 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -97,9 +97,14 @@ pub fn total_send_fees( Transfer(_) => { // Account for implicit account creation let receiver_account_type = receiver_id.get_account_type(); - let is_receiver_implicit = - config.wasm_config.implicit_account_creation && receiver_account_type.is_implicit(); - transfer_send_fee(fees, sender_is_receiver, is_receiver_implicit, receiver_account_type) + let is_receiver_implicit = config.wasm_config.implicit_account_creation + && receiver_account_type.is_implicit(); + transfer_send_fee( + fees, + sender_is_receiver, + is_receiver_implicit, + receiver_account_type, + ) } Stake(_) => fees.fee(ActionCosts::stake).send_fee(sender_is_receiver), AddKey(add_key_action) => match &add_key_action.access_key.permission { diff --git a/tools/fork-network/src/cli.rs b/tools/fork-network/src/cli.rs index a55ac733618..ee5d617d7b1 100644 --- a/tools/fork-network/src/cli.rs +++ b/tools/fork-network/src/cli.rs @@ -527,7 +527,8 @@ impl ForkNetworkCommand { } } StateRecord::PostponedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() + if receipt.predecessor_id.get_account_type().is_implicit() + || receipt.receiver_id.get_account_type().is_implicit() { let new_receipt = Receipt { predecessor_id: map_account(&receipt.predecessor_id, None), @@ -549,7 +550,8 @@ impl ForkNetworkCommand { } } StateRecord::DelayedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() + if receipt.predecessor_id.get_account_type().is_implicit() + || receipt.receiver_id.get_account_type().is_implicit() { let new_receipt = Receipt { predecessor_id: map_account(&receipt.predecessor_id, None), diff --git a/tools/mirror/src/genesis.rs b/tools/mirror/src/genesis.rs index 932c608d718..6f354d8df76 100644 --- a/tools/mirror/src/genesis.rs +++ b/tools/mirror/src/genesis.rs @@ -68,7 +68,9 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::PostponedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() { + if receipt.predecessor_id.get_account_type().is_implicit() + || receipt.receiver_id.get_account_type().is_implicit() + { receipt.predecessor_id = crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref()); receipt.receiver_id = @@ -83,7 +85,9 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::DelayedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() || receipt.receiver_id.get_account_type().is_implicit() { + if receipt.predecessor_id.get_account_type().is_implicit() + || receipt.receiver_id.get_account_type().is_implicit() + { receipt.predecessor_id = crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref()); receipt.receiver_id = diff --git a/tools/mirror/src/key_mapping.rs b/tools/mirror/src/key_mapping.rs index 92c7ee85817..9cc102e3bc4 100644 --- a/tools/mirror/src/key_mapping.rs +++ b/tools/mirror/src/key_mapping.rs @@ -1,8 +1,8 @@ use hkdf::Hkdf; use near_crypto::{ED25519PublicKey, ED25519SecretKey, PublicKey, Secp256K1PublicKey, SecretKey}; -use near_primitives_core::account::id::AccountType; use near_primitives::types::AccountId; use near_primitives::utils::derive_account_id_from_public_key; +use near_primitives_core::account::id::AccountType; use sha2::Sha256; // there is nothing special about this key, it's just some randomly generated one. @@ -106,8 +106,8 @@ pub fn map_account( PublicKey::from_near_implicit_account(account_id).expect("must be implicit"); let mapped_key = map_key(&public_key, secret); derive_account_id_from_public_key(&mapped_key.public_key()) - }, + } AccountType::EthImplicitAccount => account_id.clone(), - AccountType::NamedAccount => account_id.clone() + AccountType::NamedAccount => account_id.clone(), } } diff --git a/tools/mirror/src/lib.rs b/tools/mirror/src/lib.rs index 55094c32ef9..6f829c2161f 100644 --- a/tools/mirror/src/lib.rs +++ b/tools/mirror/src/lib.rs @@ -990,7 +990,9 @@ impl TxMirror { actions.push(Action::DeleteKey(Box::new(DeleteKeyAction { public_key }))); } Action::Transfer(_) => { - if tx.receiver_id().get_account_type().is_implicit() && source_actions.len() == 1 { + if tx.receiver_id().get_account_type().is_implicit() + && source_actions.len() == 1 + { let target_account = crate::key_mapping::map_account(tx.receiver_id(), self.secret.as_ref()); if !account_exists(&self.target_view_client, &target_account) @@ -1002,8 +1004,11 @@ impl TxMirror { ) })? { - if target_account.get_account_type() == AccountType::NearImplicitAccount { - let public_key = PublicKey::from_near_implicit_account(&target_account).expect("must be implicit"); + if target_account.get_account_type() == AccountType::NearImplicitAccount + { + let public_key = + PublicKey::from_near_implicit_account(&target_account) + .expect("must be implicit"); nonce_updates.insert((target_account, public_key)); } } From e3b385d3519dbf378fcc2fb0cf7c698be3c14984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Mon, 30 Oct 2023 16:04:39 +0100 Subject: [PATCH 03/10] Rename derive_account_id_from_public_key -> derive_near_implicit_account_id --- core/primitives/src/utils.rs | 4 ++-- .../features/access_key_nonce_for_implicit_accounts.rs | 6 +++--- integration-tests/src/tests/standard_cases/mod.rs | 6 +++--- runtime/runtime/src/actions.rs | 9 ++++----- tools/mirror/src/key_mapping.rs | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/core/primitives/src/utils.rs b/core/primitives/src/utils.rs index 50655b36e80..0dec0b1ef6b 100644 --- a/core/primitives/src/utils.rs +++ b/core/primitives/src/utils.rs @@ -471,7 +471,7 @@ where /// Derives `AccountId` from `PublicKey``. /// If the key type is ED25519, returns hex-encoded copy of the key. -pub fn derive_account_id_from_public_key(public_key: &PublicKey) -> AccountId { +pub fn derive_near_implicit_account_id(public_key: &PublicKey) -> AccountId { match public_key.key_type() { KeyType::ED25519 => hex::encode(public_key.key_data()).parse().unwrap(), _ => unimplemented!(), @@ -487,7 +487,7 @@ mod tests { let public_key = PublicKey::from_seed(KeyType::ED25519, "test"); let expected: AccountId = "bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap(); - assert_eq!(derive_account_id_from_public_key(&public_key), expected); + assert_eq!(derive_near_implicit_account_id(&public_key), expected); } #[test] diff --git a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs index 7d8acccd62e..101b32e198e 100644 --- a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs +++ b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs @@ -18,7 +18,7 @@ use near_primitives::shard_layout::ShardLayout; use near_primitives::sharding::ChunkHash; use near_primitives::transaction::SignedTransaction; use near_primitives::types::{AccountId, BlockHeight}; -use near_primitives::utils::derive_account_id_from_public_key; +use near_primitives::utils::derive_near_implicit_account_id; use near_primitives::version::{ProtocolFeature, ProtocolVersion}; use near_primitives::views::FinalExecutionStatus; use nearcore::config::GenesisExt; @@ -203,7 +203,7 @@ fn get_status_of_tx_hash_collision_for_implicit_account( fn test_transaction_hash_collision_for_implicit_account_fail() { let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version(); let secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); - let implicit_account_id = derive_account_id_from_public_key(&secret_key.public_key()); + let implicit_account_id = derive_near_implicit_account_id(&secret_key.public_key()); let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( get_status_of_tx_hash_collision_for_implicit_account( @@ -220,7 +220,7 @@ fn test_transaction_hash_collision_for_implicit_account_ok() { let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1; let secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); - let implicit_account_id = derive_account_id_from_public_key(&secret_key.public_key()); + let implicit_account_id = derive_near_implicit_account_id(&secret_key.public_key()); let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( get_status_of_tx_hash_collision_for_implicit_account( diff --git a/integration-tests/src/tests/standard_cases/mod.rs b/integration-tests/src/tests/standard_cases/mod.rs index c1b8b663285..fe67f4cfbeb 100644 --- a/integration-tests/src/tests/standard_cases/mod.rs +++ b/integration-tests/src/tests/standard_cases/mod.rs @@ -16,7 +16,7 @@ use near_primitives::errors::{ }; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::types::{AccountId, Balance, TrieNodesCount}; -use near_primitives::utils::derive_account_id_from_public_key; +use near_primitives::utils::derive_near_implicit_account_id; use near_primitives::views::{ AccessKeyView, AccountView, ExecutionMetadataView, FinalExecutionOutcomeView, FinalExecutionStatus, @@ -336,7 +336,7 @@ pub fn transfer_tokens_implicit_account(node: impl Node, public_key: PublicKey) let root = node_user.get_state_root(); let tokens_used = 10u128.pow(25); let fee_helper = fee_helper(&node); - let receiver_id = derive_account_id_from_public_key(&public_key); + let receiver_id = derive_near_implicit_account_id(&public_key); let transfer_cost = match receiver_id.get_account_type() { AccountType::NearImplicitAccount => fee_helper.create_account_transfer_full_key_cost(), @@ -401,7 +401,7 @@ pub fn trying_to_create_implicit_account(node: impl Node, public_key: PublicKey) let root = node_user.get_state_root(); let tokens_used = 10u128.pow(25); let fee_helper = fee_helper(&node); - let receiver_id = derive_account_id_from_public_key(&public_key); + let receiver_id = derive_near_implicit_account_id(&public_key); let transaction_result = node_user .create_account( diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index 89c699ca8f0..9a393a568e6 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -455,9 +455,7 @@ pub(crate) fn action_implicit_account_creation_transfer( * near_primitives::account::AccessKey::ACCESS_KEY_NONCE_RANGE_MULTIPLIER; } - // Invariant: The account_id is hex like (implicit account id). - // It holds because in the only calling site, we've checked the permissions before. - // unwrap: Can only fail if `account_id` is not NEAR-implicit. + // unwrap: here it's safe because the `account_id` has already been determined to be implicit by `get_account_type` let public_key = PublicKey::from_near_implicit_account(account_id).unwrap(); *account = Some(Account::new( @@ -472,8 +470,9 @@ pub(crate) fn action_implicit_account_creation_transfer( set_access_key(state_update, account_id.clone(), public_key, &access_key); } - AccountType::EthImplicitAccount => panic!("must be implicit"), - AccountType::NamedAccount => panic!("must be implicit"), + // Invariant: The `account_id` is implicit. + // It holds because in the only calling site, we've checked the permissions before. + AccountType::EthImplicitAccount | AccountType::NamedAccount => panic!("must be implicit"), } } diff --git a/tools/mirror/src/key_mapping.rs b/tools/mirror/src/key_mapping.rs index 9cc102e3bc4..1f2b1211e30 100644 --- a/tools/mirror/src/key_mapping.rs +++ b/tools/mirror/src/key_mapping.rs @@ -1,7 +1,7 @@ use hkdf::Hkdf; use near_crypto::{ED25519PublicKey, ED25519SecretKey, PublicKey, Secp256K1PublicKey, SecretKey}; use near_primitives::types::AccountId; -use near_primitives::utils::derive_account_id_from_public_key; +use near_primitives::utils::derive_near_implicit_account_id; use near_primitives_core::account::id::AccountType; use sha2::Sha256; @@ -105,7 +105,7 @@ pub fn map_account( let public_key = PublicKey::from_near_implicit_account(account_id).expect("must be implicit"); let mapped_key = map_key(&public_key, secret); - derive_account_id_from_public_key(&mapped_key.public_key()) + derive_near_implicit_account_id(&mapped_key.public_key()) } AccountType::EthImplicitAccount => account_id.clone(), AccountType::NamedAccount => account_id.clone(), From 1123a41dacef4bfdff67cc811c53d850bfb988e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Tue, 31 Oct 2023 12:28:27 +0100 Subject: [PATCH 04/10] nit PR fixes --- core/account-id/src/lib.rs | 15 +++++- core/primitives-core/src/runtime/fees.rs | 50 +++++++++++++------ core/primitives/src/utils.rs | 13 +++-- .../access_key_nonce_for_implicit_accounts.rs | 6 ++- .../tests/client/features/delegate_action.rs | 4 +- .../src/tests/standard_cases/mod.rs | 33 ++++++------ runtime/near-vm-runner/src/logic/logic.rs | 6 +-- runtime/runtime/src/actions.rs | 5 +- runtime/runtime/src/config.rs | 16 +++--- tools/mirror/src/key_mapping.rs | 4 +- tools/mirror/src/lib.rs | 2 +- 11 files changed, 94 insertions(+), 60 deletions(-) diff --git a/core/account-id/src/lib.rs b/core/account-id/src/lib.rs index 457188e15b1..07a867e3427 100644 --- a/core/account-id/src/lib.rs +++ b/core/account-id/src/lib.rs @@ -64,10 +64,14 @@ pub use errors::{ParseAccountError, ParseErrorKind}; #[derive(Eq, Ord, Hash, Clone, Debug, PartialEq, PartialOrd)] pub struct AccountId(Box); +/// Enum representing possible types of accounts. #[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, } @@ -75,7 +79,7 @@ impl AccountType { pub fn is_implicit(&self) -> bool { match &self { Self::NearImplicitAccount => true, - // TODO change to true later, see https://github.com/near/nearcore/issues/10018 + // TODO(eth-implicit) change to true later, see https://github.com/near/nearcore/issues/10018 Self::EthImplicitAccount => false, Self::NamedAccount => false, } @@ -150,7 +154,7 @@ impl AccountId { .map_or(false, |s| !s.contains('.')) } - /// Returns `true` if the `AccountId` is a 40 characters long hexadecimal prefixed with '0x'. + /// Returns `true` if the `AccountId` is '0x' followed by 40 hex characters (42 characters in total). /// /// See [Implicit-Accounts](https://docs.near.org/docs/concepts/account#implicit-accounts). /// @@ -475,7 +479,9 @@ mod tests { "10-4.8-2", "b-o_w_e-n", "no_lols", + // NEAR-implicit account "0123456789012345678901234567890123456789012345678901234567890123", + // ETH-implicit account "0xb794f5ea0ba39494ce839613fffba74279579268", // Valid, but can't be created "near.a", @@ -592,7 +598,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 { @@ -719,6 +727,7 @@ mod tests { ), ( "b794f5ea0ba39494ce839613fffba74279579268", + // ETH-implicit account "0xb794f5ea0ba39494ce839613fffba74279579268", ), ("aa", "ъ@aa"), @@ -764,6 +773,7 @@ mod tests { "fffff_ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", "00000000000000000000000000000000000000000000000000000000000000", + "0xb794f5ea0ba39494ce839613fffba74279579268", ]; for invalid_account_id in invalid_near_implicit_account_ids { assert!( @@ -805,6 +815,7 @@ mod tests { "0xfffff_ffffffffffffffffffffffffffffffffff", "0xoooooooooooooooooooooooooooooooooooooooo", "0x00000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", ]; for invalid_account_id in invalid_eth_implicit_account_ids { assert!( diff --git a/core/primitives-core/src/runtime/fees.rs b/core/primitives-core/src/runtime/fees.rs index aa5e245107a..3b7bf5fb819 100644 --- a/core/primitives-core/src/runtime/fees.rs +++ b/core/primitives-core/src/runtime/fees.rs @@ -208,31 +208,53 @@ impl StorageUsageConfig { /// We can assume that no overflow will happen here. pub fn transfer_exec_fee( cfg: &RuntimeFeesConfig, - is_receiver_implicit: bool, + implicit_account_creation_allowed: bool, receiver_account_type: AccountType, ) -> Gas { - let mut result = cfg.fee(ActionCosts::transfer).exec_fee(); - if is_receiver_implicit { - result += cfg.fee(ActionCosts::create_account).exec_fee(); - if receiver_account_type == AccountType::NearImplicitAccount { - result += cfg.fee(ActionCosts::add_full_access_key).exec_fee(); + let implicit_account_creation = + implicit_account_creation_allowed && receiver_account_type.is_implicit(); + if !implicit_account_creation { + // No account will be created, just a regular transfer. + cfg.fee(ActionCosts::transfer).exec_fee() + } else { + match receiver_account_type { + // Extra fees for the CreateAccount and AddFullAccessKey. + AccountType::NearImplicitAccount => { + cfg.fee(ActionCosts::transfer).exec_fee() + + cfg.fee(ActionCosts::create_account).exec_fee() + + cfg.fee(ActionCosts::add_full_access_key).exec_fee() + } + // These arms are impossible, as we checked for `is_implicit` above. + AccountType::EthImplicitAccount | AccountType::NamedAccount => { + panic!("must be near-implicit") + } } } - result } pub fn transfer_send_fee( cfg: &RuntimeFeesConfig, sender_is_receiver: bool, - is_receiver_implicit: bool, + implicit_account_creation_allowed: bool, receiver_account_type: AccountType, ) -> Gas { - let mut result = cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver); - if is_receiver_implicit { - result += cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver); - if receiver_account_type == AccountType::NearImplicitAccount { - result += cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver); + let implicit_account_creation = + implicit_account_creation_allowed && receiver_account_type.is_implicit(); + if !implicit_account_creation { + // No account will be created, just a regular transfer. + cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) + } else { + match receiver_account_type { + // Extra fees for the CreateAccount and AddFullAccessKey. + AccountType::NearImplicitAccount => { + cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) + + cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver) + + cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver) + } + // These arms are impossible, as we checked for `is_implicit` above. + AccountType::EthImplicitAccount | AccountType::NamedAccount => { + panic!("must be near-implicit") + } } } - result } diff --git a/core/primitives/src/utils.rs b/core/primitives/src/utils.rs index 0dec0b1ef6b..06894bb9eaf 100644 --- a/core/primitives/src/utils.rs +++ b/core/primitives/src/utils.rs @@ -17,7 +17,7 @@ use crate::version::{ CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION, }; -use near_crypto::{KeyType, PublicKey}; +use near_crypto::ED25519PublicKey; use near_primitives_core::account::id::AccountId; use std::mem::size_of; @@ -471,23 +471,22 @@ where /// Derives `AccountId` from `PublicKey``. /// If the key type is ED25519, returns hex-encoded copy of the key. -pub fn derive_near_implicit_account_id(public_key: &PublicKey) -> AccountId { - match public_key.key_type() { - KeyType::ED25519 => hex::encode(public_key.key_data()).parse().unwrap(), - _ => unimplemented!(), - } +pub fn derive_near_implicit_account_id(public_key: &ED25519PublicKey) -> AccountId { + hex::encode(public_key).parse().unwrap() } #[cfg(test)] mod tests { use super::*; + use near_crypto::{KeyType, PublicKey}; #[test] fn test_derive_account_id_from_ed25519_public_key() { let public_key = PublicKey::from_seed(KeyType::ED25519, "test"); let expected: AccountId = "bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap(); - assert_eq!(derive_near_implicit_account_id(&public_key), expected); + let account_id = derive_near_implicit_account_id(public_key.unwrap_as_ed25519()); + assert_eq!(account_id, expected); } #[test] diff --git a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs index 101b32e198e..bb07c943c2b 100644 --- a/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs +++ b/integration-tests/src/tests/client/features/access_key_nonce_for_implicit_accounts.rs @@ -203,7 +203,8 @@ fn get_status_of_tx_hash_collision_for_implicit_account( fn test_transaction_hash_collision_for_implicit_account_fail() { let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version(); let secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); - let implicit_account_id = derive_near_implicit_account_id(&secret_key.public_key()); + let implicit_account_id = + derive_near_implicit_account_id(secret_key.public_key().unwrap_as_ed25519()); let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( get_status_of_tx_hash_collision_for_implicit_account( @@ -220,7 +221,8 @@ fn test_transaction_hash_collision_for_implicit_account_ok() { let protocol_version = ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1; let secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); - let implicit_account_id = derive_near_implicit_account_id(&secret_key.public_key()); + let implicit_account_id = + derive_near_implicit_account_id(secret_key.public_key().unwrap_as_ed25519()); let implicit_account_signer = InMemorySigner::from_secret_key(implicit_account_id, secret_key); assert_matches!( get_status_of_tx_hash_collision_for_implicit_account( diff --git a/integration-tests/src/tests/client/features/delegate_action.rs b/integration-tests/src/tests/client/features/delegate_action.rs index 1b234b7121c..e7b7c974489 100644 --- a/integration-tests/src/tests/client/features/delegate_action.rs +++ b/integration-tests/src/tests/client/features/delegate_action.rs @@ -860,8 +860,8 @@ fn meta_tx_create_implicit_account(new_account: AccountId) { let tx_cost = match new_account.get_account_type() { AccountType::NearImplicitAccount => fee_helper.create_account_transfer_full_key_cost(), - AccountType::EthImplicitAccount => panic!("must be implicit"), - AccountType::NamedAccount => panic!("must be implicit"), + AccountType::EthImplicitAccount => panic!("must be near-implicit"), + AccountType::NamedAccount => panic!("must be near-implicit"), }; check_meta_tx_no_fn_call( &node, diff --git a/integration-tests/src/tests/standard_cases/mod.rs b/integration-tests/src/tests/standard_cases/mod.rs index fe67f4cfbeb..4a47ec2c397 100644 --- a/integration-tests/src/tests/standard_cases/mod.rs +++ b/integration-tests/src/tests/standard_cases/mod.rs @@ -336,12 +336,12 @@ pub fn transfer_tokens_implicit_account(node: impl Node, public_key: PublicKey) let root = node_user.get_state_root(); let tokens_used = 10u128.pow(25); let fee_helper = fee_helper(&node); - let receiver_id = derive_near_implicit_account_id(&public_key); + let receiver_id = derive_near_implicit_account_id(public_key.unwrap_as_ed25519()); let transfer_cost = match receiver_id.get_account_type() { AccountType::NearImplicitAccount => fee_helper.create_account_transfer_full_key_cost(), - AccountType::EthImplicitAccount => std::panic!("must be implicit"), - AccountType::NamedAccount => std::panic!("must be implicit"), + AccountType::EthImplicitAccount => std::panic!("must be near-implicit"), + AccountType::NamedAccount => std::panic!("must be near-implicit"), }; let transaction_result = @@ -369,8 +369,8 @@ pub fn transfer_tokens_implicit_account(node: impl Node, public_key: PublicKey) AccountType::NearImplicitAccount => { assert_eq!(view_access_key.unwrap(), AccessKey::full_access().into()); } - AccountType::EthImplicitAccount => std::panic!("must be implicit"), - AccountType::NamedAccount => std::panic!("must be implicit"), + AccountType::EthImplicitAccount => std::panic!("must be near-implicit"), + AccountType::NamedAccount => std::panic!("must be near-implicit"), } let transaction_result = @@ -401,7 +401,7 @@ pub fn trying_to_create_implicit_account(node: impl Node, public_key: PublicKey) let root = node_user.get_state_root(); let tokens_used = 10u128.pow(25); let fee_helper = fee_helper(&node); - let receiver_id = derive_near_implicit_account_id(&public_key); + let receiver_id = derive_near_implicit_account_id(public_key.unwrap_as_ed25519()); let transaction_result = node_user .create_account( @@ -414,17 +414,18 @@ pub fn trying_to_create_implicit_account(node: impl Node, public_key: PublicKey) let cost = match receiver_id.get_account_type() { AccountType::NearImplicitAccount => { - fee_helper.create_account_transfer_full_key_cost_fail_on_create_account() - + fee_helper.gas_to_balance( - fee_helper.cfg().fee(ActionCosts::create_account).send_fee(false) - + fee_helper - .cfg() - .fee(near_primitives::config::ActionCosts::add_full_access_key) - .send_fee(false), - ) + let fail_cost = + fee_helper.create_account_transfer_full_key_cost_fail_on_create_account(); + let create_account_fee = + fee_helper.cfg().fee(ActionCosts::create_account).send_fee(false); + let add_access_key_fee = fee_helper + .cfg() + .fee(near_primitives::config::ActionCosts::add_full_access_key) + .send_fee(false); + fail_cost + fee_helper.gas_to_balance(create_account_fee + add_access_key_fee) } - AccountType::EthImplicitAccount => std::panic!("must be implicit"), - AccountType::NamedAccount => std::panic!("must be implicit"), + AccountType::EthImplicitAccount => std::panic!("must be near-implicit"), + AccountType::NamedAccount => std::panic!("must be near-implicit"), }; assert_eq!( diff --git a/runtime/near-vm-runner/src/logic/logic.rs b/runtime/near-vm-runner/src/logic/logic.rs index 9179a9ed05b..6288ee8e5a0 100644 --- a/runtime/near-vm-runner/src/logic/logic.rs +++ b/runtime/near-vm-runner/src/logic/logic.rs @@ -1772,17 +1772,15 @@ impl<'a> VMLogic<'a> { let (receipt_idx, sir) = self.promise_idx_to_receipt_idx_with_sir(promise_idx)?; let receiver_id = self.ext.get_receipt_receiver(receipt_idx); - let is_receiver_implicit = - self.config.implicit_account_creation && receiver_id.get_account_type().is_implicit(); let send_fee = transfer_send_fee( self.fees_config, sir, - is_receiver_implicit, + self.config.implicit_account_creation, receiver_id.get_account_type(), ); let exec_fee = transfer_exec_fee( self.fees_config, - is_receiver_implicit, + self.config.implicit_account_creation, receiver_id.get_account_type(), ); let burn_gas = send_fee; diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index 9a393a568e6..c8238d752e3 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -429,6 +429,7 @@ pub(crate) fn action_create_account( )); } +/// Can only be used for NEAR-implicit accounts. pub(crate) fn action_implicit_account_creation_transfer( state_update: &mut TrieUpdate, fee_config: &RuntimeFeesConfig, @@ -472,7 +473,9 @@ pub(crate) fn action_implicit_account_creation_transfer( } // Invariant: The `account_id` is implicit. // It holds because in the only calling site, we've checked the permissions before. - AccountType::EthImplicitAccount | AccountType::NamedAccount => panic!("must be implicit"), + AccountType::EthImplicitAccount | AccountType::NamedAccount => { + panic!("must be near-implicit") + } } } diff --git a/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index e5abb47e9c4..37d9e3627e5 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -96,14 +96,11 @@ pub fn total_send_fees( } Transfer(_) => { // Account for implicit account creation - let receiver_account_type = receiver_id.get_account_type(); - let is_receiver_implicit = config.wasm_config.implicit_account_creation - && receiver_account_type.is_implicit(); transfer_send_fee( fees, sender_is_receiver, - is_receiver_implicit, - receiver_account_type, + config.wasm_config.implicit_account_creation, + receiver_id.get_account_type(), ) } Stake(_) => fees.fee(ActionCosts::stake).send_fee(sender_is_receiver), @@ -194,10 +191,11 @@ pub fn exec_fee(config: &RuntimeConfig, action: &Action, receiver_id: &AccountId } Transfer(_) => { // Account for implicit account creation - let receiver_account_type = receiver_id.get_account_type(); - let is_receiver_implicit = - config.wasm_config.implicit_account_creation && receiver_account_type.is_implicit(); - transfer_exec_fee(fees, is_receiver_implicit, receiver_account_type) + transfer_exec_fee( + fees, + config.wasm_config.implicit_account_creation, + receiver_id.get_account_type(), + ) } Stake(_) => fees.fee(ActionCosts::stake).exec_fee(), AddKey(add_key_action) => match &add_key_action.access_key.permission { diff --git a/tools/mirror/src/key_mapping.rs b/tools/mirror/src/key_mapping.rs index 1f2b1211e30..4cbc8608419 100644 --- a/tools/mirror/src/key_mapping.rs +++ b/tools/mirror/src/key_mapping.rs @@ -103,9 +103,9 @@ pub fn map_account( match account_id.get_account_type() { AccountType::NearImplicitAccount => { let public_key = - PublicKey::from_near_implicit_account(account_id).expect("must be implicit"); + PublicKey::from_near_implicit_account(account_id).expect("must be near-implicit"); let mapped_key = map_key(&public_key, secret); - derive_near_implicit_account_id(&mapped_key.public_key()) + derive_near_implicit_account_id(mapped_key.public_key().unwrap_as_ed25519()) } AccountType::EthImplicitAccount => account_id.clone(), AccountType::NamedAccount => account_id.clone(), diff --git a/tools/mirror/src/lib.rs b/tools/mirror/src/lib.rs index 6f829c2161f..c09a4b75b33 100644 --- a/tools/mirror/src/lib.rs +++ b/tools/mirror/src/lib.rs @@ -1008,7 +1008,7 @@ impl TxMirror { { let public_key = PublicKey::from_near_implicit_account(&target_account) - .expect("must be implicit"); + .expect("must be near-implicit"); nonce_updates.insert((target_account, public_key)); } } From 6a3895cf21b9baecc28ac219026cb7c19b52eb1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Tue, 31 Oct 2023 16:04:57 +0100 Subject: [PATCH 05/10] transfer_exec_fee / transfer_send_fee refactor --- core/primitives-core/src/runtime/fees.rs | 58 ++++++++++-------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/core/primitives-core/src/runtime/fees.rs b/core/primitives-core/src/runtime/fees.rs index 3b7bf5fb819..5e01fcb8d4b 100644 --- a/core/primitives-core/src/runtime/fees.rs +++ b/core/primitives-core/src/runtime/fees.rs @@ -211,24 +211,19 @@ pub fn transfer_exec_fee( implicit_account_creation_allowed: bool, receiver_account_type: AccountType, ) -> Gas { - let implicit_account_creation = - implicit_account_creation_allowed && receiver_account_type.is_implicit(); - if !implicit_account_creation { + let transfer_fee = cfg.fee(ActionCosts::transfer).exec_fee(); + let create_account_fee = cfg.fee(ActionCosts::create_account).exec_fee(); + let add_access_key_fee = cfg.fee(ActionCosts::add_full_access_key).exec_fee(); + + match (implicit_account_creation_allowed, receiver_account_type) { + // Regular transfer to a named account. + (_, AccountType::NamedAccount) => transfer_fee, // No account will be created, just a regular transfer. - cfg.fee(ActionCosts::transfer).exec_fee() - } else { - match receiver_account_type { - // Extra fees for the CreateAccount and AddFullAccessKey. - AccountType::NearImplicitAccount => { - cfg.fee(ActionCosts::transfer).exec_fee() - + cfg.fee(ActionCosts::create_account).exec_fee() - + cfg.fee(ActionCosts::add_full_access_key).exec_fee() - } - // These arms are impossible, as we checked for `is_implicit` above. - AccountType::EthImplicitAccount | AccountType::NamedAccount => { - panic!("must be near-implicit") - } - } + (false, _) => transfer_fee, + // Currently, no account is created on transfer to ETH-implicit account, just a regular transfer. + (true, AccountType::EthImplicitAccount) => transfer_fee, + // Extra fees for the CreateAccount and AddFullAccessKey. + (true, AccountType::NearImplicitAccount) => transfer_fee + create_account_fee + add_access_key_fee, } } @@ -238,23 +233,18 @@ pub fn transfer_send_fee( implicit_account_creation_allowed: bool, receiver_account_type: AccountType, ) -> Gas { - let implicit_account_creation = - implicit_account_creation_allowed && receiver_account_type.is_implicit(); - if !implicit_account_creation { + let transfer_fee = cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver); + let create_account_fee = cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver); + let add_access_key_fee = cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver); + + match (implicit_account_creation_allowed, receiver_account_type) { + // Regular transfer to a named account. + (_, AccountType::NamedAccount) => transfer_fee, // No account will be created, just a regular transfer. - cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) - } else { - match receiver_account_type { - // Extra fees for the CreateAccount and AddFullAccessKey. - AccountType::NearImplicitAccount => { - cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver) - + cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver) - + cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver) - } - // These arms are impossible, as we checked for `is_implicit` above. - AccountType::EthImplicitAccount | AccountType::NamedAccount => { - panic!("must be near-implicit") - } - } + (false, _) => transfer_fee, + // Currently, no account is created on transfer to ETH-implicit account, just a regular transfer. + (true, AccountType::EthImplicitAccount) => transfer_fee, + // Extra fees for the CreateAccount and AddFullAccessKey. + (true, AccountType::NearImplicitAccount) => transfer_fee + create_account_fee + add_access_key_fee, } } From dc0d40f0de99a6207dae17ccf157dbbf0bfb795f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Tue, 31 Oct 2023 17:24:15 +0100 Subject: [PATCH 06/10] Style fix for cargo fmt check --- core/primitives-core/src/runtime/fees.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/primitives-core/src/runtime/fees.rs b/core/primitives-core/src/runtime/fees.rs index 5e01fcb8d4b..a2b7bf5acd5 100644 --- a/core/primitives-core/src/runtime/fees.rs +++ b/core/primitives-core/src/runtime/fees.rs @@ -212,9 +212,6 @@ pub fn transfer_exec_fee( receiver_account_type: AccountType, ) -> Gas { let transfer_fee = cfg.fee(ActionCosts::transfer).exec_fee(); - let create_account_fee = cfg.fee(ActionCosts::create_account).exec_fee(); - let add_access_key_fee = cfg.fee(ActionCosts::add_full_access_key).exec_fee(); - match (implicit_account_creation_allowed, receiver_account_type) { // Regular transfer to a named account. (_, AccountType::NamedAccount) => transfer_fee, @@ -223,7 +220,11 @@ pub fn transfer_exec_fee( // Currently, no account is created on transfer to ETH-implicit account, just a regular transfer. (true, AccountType::EthImplicitAccount) => transfer_fee, // Extra fees for the CreateAccount and AddFullAccessKey. - (true, AccountType::NearImplicitAccount) => transfer_fee + create_account_fee + add_access_key_fee, + (true, AccountType::NearImplicitAccount) => { + transfer_fee + + cfg.fee(ActionCosts::create_account).exec_fee() + + cfg.fee(ActionCosts::add_full_access_key).exec_fee() + } } } @@ -234,9 +235,6 @@ pub fn transfer_send_fee( receiver_account_type: AccountType, ) -> Gas { let transfer_fee = cfg.fee(ActionCosts::transfer).send_fee(sender_is_receiver); - let create_account_fee = cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver); - let add_access_key_fee = cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver); - match (implicit_account_creation_allowed, receiver_account_type) { // Regular transfer to a named account. (_, AccountType::NamedAccount) => transfer_fee, @@ -245,6 +243,10 @@ pub fn transfer_send_fee( // Currently, no account is created on transfer to ETH-implicit account, just a regular transfer. (true, AccountType::EthImplicitAccount) => transfer_fee, // Extra fees for the CreateAccount and AddFullAccessKey. - (true, AccountType::NearImplicitAccount) => transfer_fee + create_account_fee + add_access_key_fee, + (true, AccountType::NearImplicitAccount) => { + transfer_fee + + cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver) + + cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver) + } } } From 0e82e4828374424e1b4809e635585214501de330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Thu, 2 Nov 2023 10:45:40 +0100 Subject: [PATCH 07/10] Remove core/account-id/src/lib.rs --- Cargo.lock | 40 +- core/account-id/src/lib.rs | 853 ------------------------------------- 2 files changed, 16 insertions(+), 877 deletions(-) delete mode 100644 core/account-id/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 494c7f2d04a..cd8ba1f068a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -725,9 +725,8 @@ dependencies = [ [[package]] name = "bolero" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9fec67acd9afcd579067cc506c537da49751b8b81c98d5a5e15ba1e853aa3c" +version = "0.9.0" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "bolero-afl", "bolero-engine", @@ -741,9 +740,8 @@ dependencies = [ [[package]] name = "bolero-afl" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b34f05de1527425bb05287da09ff1ff1612538648824db49e16d9693b24065" +version = "0.9.0" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "bolero-engine", "cc", @@ -751,9 +749,8 @@ dependencies = [ [[package]] name = "bolero-engine" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ddcfa4c2aa7d57b1785c6e258f612e74c96afa078300d0f811dee73592d7ca" +version = "0.9.1" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "anyhow", "backtrace", @@ -765,9 +762,8 @@ dependencies = [ [[package]] name = "bolero-generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8efabd99549391e8b372e8afe566e8236ca4be6be379c1b6bf81b027c472fe7" +version = "0.9.1" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "arbitrary", "bolero-generator-derive", @@ -777,9 +773,8 @@ dependencies = [ [[package]] name = "bolero-generator-derive" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53397bfda19ccb48527faa14025048fc4bb76f090ccdeef1e5a355bfe4a94467" +version = "0.9.2" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -789,27 +784,24 @@ dependencies = [ [[package]] name = "bolero-honggfuzz" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf78581db1a7263620a8767e645b93ad287c70122ae76f5bd67040c7f06ff8e3" +version = "0.9.0" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "bolero-engine", ] [[package]] name = "bolero-kani" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e55cec272a617f5ae4ce670db035108eb97c10cd4f67de851a3c8d3f18f19cb" +version = "0.9.0" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "bolero-engine", ] [[package]] name = "bolero-libfuzzer" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb42f66ee3ec89b9c411994de59d4710ced19df96fea2059feea1c2d73904c5b" +version = "0.9.0" +source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" dependencies = [ "bolero-engine", "cc", diff --git a/core/account-id/src/lib.rs b/core/account-id/src/lib.rs deleted file mode 100644 index 07a867e3427..00000000000 --- a/core/account-id/src/lib.rs +++ /dev/null @@ -1,853 +0,0 @@ -//! This crate provides a type for representing a syntactically valid, unique account identifier on the [NEAR](https://near.org) network. -//! -//! ## Account ID Rules -//! -//! - Minimum length is `2` -//! - Maximum length is `64` -//! - An **Account ID** consists of **Account ID parts** separated by `.`, example: -//! - `root` ✔ -//! - `alice.near` ✔ -//! - `app.stage.testnet` ✔ -//! - Must not start or end with separators (`_`, `-` or `.`): -//! - `_alice.` ✗ -//! - `.bob.near-` ✗ -//! - Each part of the **Account ID** consists of lowercase alphanumeric symbols separated either by `_` or `-`, example: -//! - `ƒelicia.near` ✗ (`ƒ` is not `f`) -//! - `1_4m_n0t-al1c3.near` ✔ -//! - Separators are not permitted to immediately follow each other, example: -//! - `alice..near` ✗ -//! - `not-_alice.near` ✗ -//! - An **Account ID** that is 64 characters long and consists of lowercase hex characters is a specific **implicit account ID** -//! -//! Learn more here: -//! -//! Also see [Error kind precedence](AccountId#error-kind-precedence). -//! -//! ## Usage -//! -//! ``` -//! use near_account_id::AccountId; -//! -//! let alice: AccountId = "alice.near".parse().unwrap(); -//! -//! assert!("ƒelicia.near".parse::().is_err()); // (ƒ is not f) -//! ``` - -use std::{fmt, str::FromStr}; - -mod errors; - -#[cfg(feature = "borsh")] -mod borsh; -#[cfg(feature = "serde")] -mod serde; - -pub use errors::{ParseAccountError, ParseErrorKind}; - -/// NEAR Account Identifier. -/// -/// This is a unique, syntactically valid, human-readable account identifier on the NEAR network. -/// -/// [See the crate-level docs for information about validation.](index.html#account-id-rules) -/// -/// Also see [Error kind precedence](AccountId#error-kind-precedence). -/// -/// ## Examples -/// -/// ``` -/// use near_account_id::AccountId; -/// -/// let alice: AccountId = "alice.near".parse().unwrap(); -/// -/// assert!("ƒelicia.near".parse::().is_err()); // (ƒ is not f) -/// ``` -#[derive(Eq, Ord, Hash, Clone, Debug, PartialEq, PartialOrd)] -pub struct AccountId(Box); - -/// Enum representing possible types of accounts. -#[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, - // TODO(eth-implicit) change to true later, see https://github.com/near/nearcore/issues/10018 - Self::EthImplicitAccount => false, - Self::NamedAccount => false, - } - } -} - -impl AccountId { - /// Shortest valid length for a NEAR Account ID. - pub const MIN_LEN: usize = 2; - /// Longest valid length for a NEAR Account ID. - pub const MAX_LEN: usize = 64; - - /// Returns a string slice of the entire Account ID. - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::AccountId; - /// - /// let carol: AccountId = "carol.near".parse().unwrap(); - /// assert_eq!("carol.near", carol.as_str()); - /// ``` - pub fn as_str(&self) -> &str { - self - } - - /// Returns `true` if the `AccountId` is a top-level NEAR Account ID. - /// - /// See [Top-level Accounts](https://docs.near.org/docs/concepts/account#top-level-accounts). - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::AccountId; - /// - /// let near_tla: AccountId = "near".parse().unwrap(); - /// assert!(near_tla.is_top_level()); - /// - /// // "alice.near" is a sub account of "near" account - /// let alice: AccountId = "alice.near".parse().unwrap(); - /// assert!(!alice.is_top_level()); - /// ``` - pub fn is_top_level(&self) -> bool { - !self.is_system() && !self.contains('.') - } - - /// Returns `true` if the `AccountId` is a direct sub-account of the provided parent account. - /// - /// See [Subaccounts](https://docs.near.org/docs/concepts/account#subaccounts). - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::AccountId; - /// - /// let near_tla: AccountId = "near".parse().unwrap(); - /// assert!(near_tla.is_top_level()); - /// - /// let alice: AccountId = "alice.near".parse().unwrap(); - /// assert!(alice.is_sub_account_of(&near_tla)); - /// - /// let alice_app: AccountId = "app.alice.near".parse().unwrap(); - /// - /// // While app.alice.near is a sub account of alice.near, - /// // app.alice.near is not a sub account of near - /// assert!(alice_app.is_sub_account_of(&alice)); - /// assert!(!alice_app.is_sub_account_of(&near_tla)); - /// ``` - pub fn is_sub_account_of(&self, parent: &AccountId) -> bool { - self.strip_suffix(parent.as_str()) - .and_then(|s| s.strip_suffix('.')) - .map_or(false, |s| !s.contains('.')) - } - - /// Returns `true` if the `AccountId` is '0x' followed by 40 hex characters (42 characters in total). - /// - /// See [Implicit-Accounts](https://docs.near.org/docs/concepts/account#implicit-accounts). - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::AccountId; - /// - /// let alice: AccountId = "alice.near".parse().unwrap(); - /// assert!(!alice.is_eth_implicit()); - /// - /// let rando = "0xb794f5ea0ba39494ce839613fffba74279579268" - /// .parse::() - /// .unwrap(); - /// assert!(rando.is_eth_implicit()); - /// ``` - fn is_eth_implicit(&self) -> bool { - self.len() == 42 - && self.starts_with("0x") - && self[2..].as_bytes().iter().all(|b| matches!(b, b'a'..=b'f' | b'0'..=b'9')) - } - - /// Returns `true` if the `AccountId` is a 64 characters long hexadecimal. - /// - /// See [Implicit-Accounts](https://docs.near.org/docs/concepts/account#implicit-accounts). - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::AccountId; - /// - /// let alice: AccountId = "alice.near".parse().unwrap(); - /// assert!(!alice.is_near_implicit()); - /// - /// let rando = "98793cd91a3f870fb126f66285808c7e094afcfc4eda8a970f6648cdf0dbd6de" - /// .parse::() - /// .unwrap(); - /// assert!(rando.is_near_implicit()); - /// ``` - fn is_near_implicit(&self) -> bool { - self.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 self.is_eth_implicit() { - return AccountType::EthImplicitAccount; - } - if self.is_near_implicit() { - return AccountType::NearImplicitAccount; - } - AccountType::NamedAccount - } - - /// Returns `true` if this `AccountId` is the system account. - /// - /// See [System account](https://nomicon.io/DataStructures/Account.html?highlight=system#system-account). - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::AccountId; - /// - /// let alice: AccountId = "alice.near".parse().unwrap(); - /// assert!(!alice.is_system()); - /// - /// let system: AccountId = "system".parse().unwrap(); - /// assert!(system.is_system()); - /// ``` - pub fn is_system(&self) -> bool { - self.as_str() == "system" - } - - /// Validates a string as a well-structured NEAR Account ID. - /// - /// Checks Account ID validity without constructing an `AccountId` instance. - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::{AccountId, ParseErrorKind}; - /// - /// assert!(AccountId::validate("alice.near").is_ok()); - /// - /// assert!( - /// matches!( - /// AccountId::validate("ƒelicia.near"), // fancy ƒ! - /// Err(err) if err.kind() == &ParseErrorKind::InvalidChar - /// ) - /// ); - /// ``` - /// - /// ## Error kind precedence - /// - /// If an Account ID has multiple format violations, the first one would be reported. - /// - /// ### Examples - /// - /// ``` - /// use near_account_id::{AccountId, ParseErrorKind}; - /// - /// assert!( - /// matches!( - /// AccountId::validate("A__ƒƒluent."), - /// Err(err) if err.kind() == &ParseErrorKind::InvalidChar - /// ) - /// ); - /// - /// assert!( - /// matches!( - /// AccountId::validate("a__ƒƒluent."), - /// Err(err) if err.kind() == &ParseErrorKind::RedundantSeparator - /// ) - /// ); - /// - /// assert!( - /// matches!( - /// AccountId::validate("aƒƒluent."), - /// Err(err) if err.kind() == &ParseErrorKind::InvalidChar - /// ) - /// ); - /// - /// assert!( - /// matches!( - /// AccountId::validate("affluent."), - /// Err(err) if err.kind() == &ParseErrorKind::RedundantSeparator - /// ) - /// ); - /// ``` - pub fn validate(account_id: &str) -> Result<(), ParseAccountError> { - if account_id.len() < AccountId::MIN_LEN { - Err(ParseAccountError { kind: ParseErrorKind::TooShort, char: None }) - } else if account_id.len() > AccountId::MAX_LEN { - Err(ParseAccountError { kind: ParseErrorKind::TooLong, char: None }) - } else { - // Adapted from https://github.com/near/near-sdk-rs/blob/fd7d4f82d0dfd15f824a1cf110e552e940ea9073/near-sdk/src/environment/env.rs#L819 - - // NOTE: We don't want to use Regex here, because it requires extra time to compile it. - // The valid account ID regex is /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/ - // Instead the implementation is based on the previous character checks. - - // We can safely assume that last char was a separator. - let mut last_char_is_separator = true; - - let mut this = None; - for (i, c) in account_id.chars().enumerate() { - this.replace((i, c)); - let current_char_is_separator = match c { - 'a'..='z' | '0'..='9' => false, - '-' | '_' | '.' => true, - _ => { - return Err(ParseAccountError { - kind: ParseErrorKind::InvalidChar, - char: this, - }); - } - }; - if current_char_is_separator && last_char_is_separator { - return Err(ParseAccountError { - kind: ParseErrorKind::RedundantSeparator, - char: this, - }); - } - last_char_is_separator = current_char_is_separator; - } - - if last_char_is_separator { - return Err(ParseAccountError { - kind: ParseErrorKind::RedundantSeparator, - char: this, - }); - } - Ok(()) - } - } - - /// Creates an `AccountId` without any validation checks. - /// - /// Please note that this is restrictively for internal use only. Plus, being behind a feature flag, - /// this could be removed later in the future. - /// - /// ## Safety - /// - /// Since this skips validation and constructs an `AccountId` regardless, - /// the caller bears the responsibility of ensuring that the Account ID is valid. - /// You can use the [`AccountId::validate`] function sometime after its creation but before it's use. - /// - /// ## Examples - /// - /// ``` - /// use near_account_id::AccountId; - /// - /// let alice = AccountId::new_unvalidated("alice.near".to_string()); - /// assert!(AccountId::validate(alice.as_str()).is_ok()); - /// - /// let ƒelicia = AccountId::new_unvalidated("ƒelicia.near".to_string()); - /// assert!(AccountId::validate(ƒelicia.as_str()).is_err()); - /// ``` - #[doc(hidden)] - #[cfg(feature = "internal_unstable")] - #[deprecated = "AccountId construction without validation is illegal since #4440"] - pub fn new_unvalidated(account_id: String) -> Self { - Self(account_id.into_boxed_str()) - } -} - -impl std::ops::Deref for AccountId { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0.as_ref() - } -} - -impl AsRef for AccountId { - fn as_ref(&self) -> &str { - self - } -} - -impl std::borrow::Borrow for AccountId { - fn borrow(&self) -> &str { - self - } -} - -impl FromStr for AccountId { - type Err = ParseAccountError; - - fn from_str(account_id: &str) -> Result { - Self::validate(account_id)?; - Ok(Self(account_id.into())) - } -} - -impl TryFrom> for AccountId { - type Error = ParseAccountError; - - fn try_from(account_id: Box) -> Result { - Self::validate(&account_id)?; - Ok(Self(account_id)) - } -} - -impl TryFrom for AccountId { - type Error = ParseAccountError; - - fn try_from(account_id: String) -> Result { - Self::validate(&account_id)?; - Ok(Self(account_id.into_boxed_str())) - } -} - -impl fmt::Display for AccountId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} - -impl From for String { - fn from(account_id: AccountId) -> Self { - account_id.0.into_string() - } -} - -impl From for Box { - fn from(value: AccountId) -> Box { - value.0 - } -} - -#[cfg(feature = "arbitrary")] -impl<'a> arbitrary::Arbitrary<'a> for AccountId { - fn size_hint(_depth: usize) -> (usize, Option) { - (AccountId::MIN_LEN, Some(AccountId::MAX_LEN)) - } - - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let mut s = u.arbitrary::<&str>()?; - loop { - match s.parse::() { - Ok(account_id) => break Ok(account_id), - Err(ParseAccountError { char: Some((idx, _)), .. }) => { - s = &s[..idx]; - continue; - } - _ => break Err(arbitrary::Error::IncorrectFormat), - } - } - } - - fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result { - <&str as arbitrary::Arbitrary>::arbitrary_take_rest(u)? - .parse() - .map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - pub const OK_ACCOUNT_IDS: [&str; 25] = [ - "aa", - "a-a", - "a-aa", - "100", - "0o", - "com", - "near", - "bowen", - "b-o_w_e-n", - "b.owen", - "bro.wen", - "a.ha", - "a.b-a.ra", - "system", - "over.9000", - "google.com", - "illia.cheapaccounts.near", - "0o0ooo00oo00o", - "alex-skidanov", - "10-4.8-2", - "b-o_w_e-n", - "no_lols", - // NEAR-implicit account - "0123456789012345678901234567890123456789012345678901234567890123", - // ETH-implicit account - "0xb794f5ea0ba39494ce839613fffba74279579268", - // Valid, but can't be created - "near.a", - ]; - - pub const BAD_ACCOUNT_IDS: [&str; 24] = [ - "a", - "A", - "Abc", - "-near", - "near-", - "-near-", - "near.", - ".near", - "near@", - "@near", - "неар", - "@@@@@", - "0__0", - "0_-_0", - "0_-_0", - "..", - "a..near", - "nEar", - "_bowen", - "hello world", - "abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz", - "01234567890123456789012345678901234567890123456789012345678901234", - // `@` separators are banned now - "some-complex-address@gmail.com", - "sub.buy_d1gitz@atata@b0-rg.c_0_m", - ]; - - #[test] - fn test_is_valid_account_id() { - for account_id in OK_ACCOUNT_IDS.iter().cloned() { - if let Err(err) = AccountId::validate(account_id) { - panic!("Valid account id {:?} marked invalid: {}", account_id, err.kind()); - } - } - - for account_id in BAD_ACCOUNT_IDS.iter().cloned() { - if AccountId::validate(account_id).is_ok() { - panic!("Invalid account id {:?} marked valid", account_id); - } - } - } - - #[test] - fn test_err_kind_classification() { - let id = "ErinMoriarty.near".parse::(); - debug_assert!( - matches!( - id, - Err(ParseAccountError { kind: ParseErrorKind::InvalidChar, char: Some((0, 'E')) }) - ), - "{:?}", - id - ); - - let id = "-KarlUrban.near".parse::(); - debug_assert!( - matches!( - id, - Err(ParseAccountError { - kind: ParseErrorKind::RedundantSeparator, - char: Some((0, '-')) - }) - ), - "{:?}", - id - ); - - let id = "anthonystarr.".parse::(); - debug_assert!( - matches!( - id, - Err(ParseAccountError { - kind: ParseErrorKind::RedundantSeparator, - char: Some((12, '.')) - }) - ), - "{:?}", - id - ); - - let id = "jack__Quaid.near".parse::(); - debug_assert!( - matches!( - id, - Err(ParseAccountError { - kind: ParseErrorKind::RedundantSeparator, - char: Some((5, '_')) - }) - ), - "{:?}", - id - ); - } - - #[test] - fn test_is_valid_top_level_account_id() { - let ok_top_level_account_ids = &[ - "aa", - "a-a", - "a-aa", - "100", - "0o", - "com", - "near", - "bowen", - "b-o_w_e-n", - "0o0ooo00oo00o", - "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 { - assert!( - account_id - .parse::() - .map_or(false, |account_id| account_id.is_top_level()), - "Valid top level account id {:?} marked invalid", - account_id - ); - } - - let bad_top_level_account_ids = &[ - "ƒelicia.near", // fancy ƒ! - "near.a", - "b.owen", - "bro.wen", - "a.ha", - "a.b-a.ra", - "some-complex-address@gmail.com", - "sub.buy_d1gitz@atata@b0-rg.c_0_m", - "over.9000", - "google.com", - "illia.cheapaccounts.near", - "10-4.8-2", - "a", - "A", - "Abc", - "-near", - "near-", - "-near-", - "near.", - ".near", - "near@", - "@near", - "неар", - "@@@@@", - "0__0", - "0_-_0", - "0_-_0", - "..", - "a..near", - "nEar", - "_bowen", - "hello world", - "abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz", - "01234567890123456789012345678901234567890123456789012345678901234", - // Valid regex and length, but reserved - "system", - ]; - for account_id in bad_top_level_account_ids { - assert!( - !account_id - .parse::() - .map_or(false, |account_id| account_id.is_top_level()), - "Invalid top level account id {:?} marked valid", - account_id - ); - } - } - - #[test] - fn test_is_valid_sub_account_id() { - let ok_pairs = &[ - ("test", "a.test"), - ("test-me", "abc.test-me"), - ("gmail.com", "abc.gmail.com"), - ("gmail.com", "abc-lol.gmail.com"), - ("gmail.com", "abc_lol.gmail.com"), - ("gmail.com", "bro-abc_lol.gmail.com"), - ("g0", "0g.g0"), - ("1g", "1g.1g"), - ("5-3", "4_2.5-3"), - ]; - for (signer_id, sub_account_id) in ok_pairs { - assert!( - matches!( - (signer_id.parse::(), sub_account_id.parse::()), - (Ok(signer_id), Ok(sub_account_id)) if sub_account_id.is_sub_account_of(&signer_id) - ), - "Failed to create sub-account {:?} by account {:?}", - sub_account_id, - signer_id - ); - } - - let bad_pairs = &[ - ("test", ".test"), - ("test", "test"), - ("test", "a1.a.test"), - ("test", "est"), - ("test", ""), - ("test", "st"), - ("test5", "ббб"), - ("test", "a-test"), - ("test", "etest"), - ("test", "a.etest"), - ("test", "retest"), - ("test-me", "abc-.test-me"), - ("test-me", "Abc.test-me"), - ("test-me", "-abc.test-me"), - ("test-me", "a--c.test-me"), - ("test-me", "a_-c.test-me"), - ("test-me", "a-_c.test-me"), - ("test-me", "_abc.test-me"), - ("test-me", "abc_.test-me"), - ("test-me", "..test-me"), - ("test-me", "a..test-me"), - ("gmail.com", "a.abc@gmail.com"), - ("gmail.com", ".abc@gmail.com"), - ("gmail.com", ".abc@gmail@com"), - ("gmail.com", "abc@gmail@com"), - ("test", "a@test"), - ("test_me", "abc@test_me"), - ("gmail.com", "abc@gmail.com"), - ("gmail@com", "abc.gmail@com"), - ("gmail.com", "abc-lol@gmail.com"), - ("gmail@com", "abc_lol.gmail@com"), - ("gmail@com", "bro-abc_lol.gmail@com"), - ("gmail.com", "123456789012345678901234567890123456789012345678901234567890@gmail.com"), - ( - "123456789012345678901234567890123456789012345678901234567890", - "1234567890.123456789012345678901234567890123456789012345678901234567890", - ), - ( - "b794f5ea0ba39494ce839613fffba74279579268", - // ETH-implicit account - "0xb794f5ea0ba39494ce839613fffba74279579268", - ), - ("aa", "ъ@aa"), - ("aa", "ъ.aa"), - ]; - for (signer_id, sub_account_id) in bad_pairs { - assert!( - !matches!( - (signer_id.parse::(), sub_account_id.parse::()), - (Ok(signer_id), Ok(sub_account_id)) if sub_account_id.is_sub_account_of(&signer_id) - ), - "Invalid sub-account {:?} created by account {:?}", - sub_account_id, - signer_id - ); - } - } - - #[test] - fn test_is_account_id_near_implicit() { - let valid_near_implicit_account_ids = &[ - "0000000000000000000000000000000000000000000000000000000000000000", - "6174617461746174617461746174617461746174617461746174617461746174", - "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "20782e20662e64666420482123494b6b6c677573646b6c66676a646b6c736667", - ]; - for valid_account_id in valid_near_implicit_account_ids { - assert!( - matches!( - valid_account_id.parse::(), - Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount - ), - "Account ID {} should be valid 64-len hex", - valid_account_id - ); - } - - let invalid_near_implicit_account_ids = &[ - "000000000000000000000000000000000000000000000000000000000000000", - "6.74617461746174617461746174617461746174617461746174617461746174", - "012-456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - "fffff_ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", - "00000000000000000000000000000000000000000000000000000000000000", - "0xb794f5ea0ba39494ce839613fffba74279579268", - ]; - for invalid_account_id in invalid_near_implicit_account_ids { - assert!( - !matches!( - invalid_account_id.parse::(), - Ok(account_id) if account_id.get_account_type() == AccountType::NearImplicitAccount - ), - "Account ID {} is not an 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::(), - 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::(), - Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount - ), - "Account ID {} is not an implicit account", - invalid_account_id - ); - } - } - - #[test] - #[cfg(feature = "arbitrary")] - fn test_arbitrary() { - let corpus = [ - ("a|bcd", None), - ("ab|cde", Some("ab")), - ("a_-b", None), - ("ab_-c", Some("ab")), - ("a", None), - ("miraclx.near", Some("miraclx.near")), - ("01234567890123456789012345678901234567890123456789012345678901234", None), - ]; - - for (input, expected_output) in corpus { - assert!(input.len() <= u8::MAX as usize); - let data = [input.as_bytes(), &[input.len() as _]].concat(); - let mut u = arbitrary::Unstructured::new(&data); - - assert_eq!(u.arbitrary::().as_deref().ok(), expected_output); - } - } -} From 861bdcacf12b2a965e3cf6e92545ead499cfda81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Sun, 5 Nov 2023 21:14:01 +0100 Subject: [PATCH 08/10] Update near-account-id version --- Cargo.lock | 44 +++++++++++-------- Cargo.toml | 2 +- .../tests/client/features/delegate_action.rs | 4 +- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd8ba1f068a..a6220e9c310 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -725,8 +725,9 @@ dependencies = [ [[package]] name = "bolero" -version = "0.9.0" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9fec67acd9afcd579067cc506c537da49751b8b81c98d5a5e15ba1e853aa3c" dependencies = [ "bolero-afl", "bolero-engine", @@ -740,8 +741,9 @@ dependencies = [ [[package]] name = "bolero-afl" -version = "0.9.0" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b34f05de1527425bb05287da09ff1ff1612538648824db49e16d9693b24065" dependencies = [ "bolero-engine", "cc", @@ -749,8 +751,9 @@ dependencies = [ [[package]] name = "bolero-engine" -version = "0.9.1" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ddcfa4c2aa7d57b1785c6e258f612e74c96afa078300d0f811dee73592d7ca" dependencies = [ "anyhow", "backtrace", @@ -762,8 +765,9 @@ dependencies = [ [[package]] name = "bolero-generator" -version = "0.9.1" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8efabd99549391e8b372e8afe566e8236ca4be6be379c1b6bf81b027c472fe7" dependencies = [ "arbitrary", "bolero-generator-derive", @@ -773,8 +777,9 @@ dependencies = [ [[package]] name = "bolero-generator-derive" -version = "0.9.2" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53397bfda19ccb48527faa14025048fc4bb76f090ccdeef1e5a355bfe4a94467" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -784,24 +789,27 @@ dependencies = [ [[package]] name = "bolero-honggfuzz" -version = "0.9.0" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf78581db1a7263620a8767e645b93ad287c70122ae76f5bd67040c7f06ff8e3" dependencies = [ "bolero-engine", ] [[package]] name = "bolero-kani" -version = "0.9.0" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e55cec272a617f5ae4ce670db035108eb97c10cd4f67de851a3c8d3f18f19cb" dependencies = [ "bolero-engine", ] [[package]] name = "bolero-libfuzzer" -version = "0.9.0" -source = "git+https://github.com/camshaft/bolero?rev=7d955a67fbd06f61ffaf28178e976280c33c045a#7d955a67fbd06f61ffaf28178e976280c33c045a" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb42f66ee3ec89b9c411994de59d4710ced19df96fea2059feea1c2d73904c5b" dependencies = [ "bolero-engine", "cc", @@ -3342,9 +3350,9 @@ dependencies = [ [[package]] name = "near-account-id" -version = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e328ed1bf9045c46595cc88274a46a6412e3a070c2ad3c146e2f94ed150a7c2" +checksum = "2df682a64c06590d210725268a738a88321536d76beb02f0465bfbdc379f0cbf" dependencies = [ "borsh 1.0.0", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7595cf7674a..16e0789e074 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,7 +172,7 @@ lru = "0.7.2" memmap2 = "0.5" memoffset = "0.8" more-asserts = "0.2" -near-account-id = { version = "1.0.0-alpha.1", features = ["internal_unstable", "serde", "borsh"] } +near-account-id = { version = "1.0.0-alpha.2", features = ["internal_unstable", "serde", "borsh"] } near-actix-test-utils = { path = "test-utils/actix-test-utils" } near-amend-genesis = { path = "tools/amend-genesis" } near-database-tool = { path = "tools/database" } diff --git a/integration-tests/src/tests/client/features/delegate_action.rs b/integration-tests/src/tests/client/features/delegate_action.rs index e7b7c974489..31e47c3fbbf 100644 --- a/integration-tests/src/tests/client/features/delegate_action.rs +++ b/integration-tests/src/tests/client/features/delegate_action.rs @@ -137,7 +137,9 @@ fn check_meta_tx_execution( .nonce; let user_pubk = match sender.get_account_type() { AccountType::NearImplicitAccount => PublicKey::from_near_implicit_account(&sender).unwrap(), - AccountType::EthImplicitAccount => PublicKey::from_seed(KeyType::SECP256K1, sender.as_ref()), + AccountType::EthImplicitAccount => { + PublicKey::from_seed(KeyType::SECP256K1, sender.as_ref()) + } AccountType::NamedAccount => PublicKey::from_seed(KeyType::ED25519, sender.as_ref()), }; let user_nonce_before = node_user.get_access_key(&sender, &user_pubk).unwrap().nonce; From d9b8e5311cbc9311826aa9b30795617e2487e257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Sun, 5 Nov 2023 21:25:21 +0100 Subject: [PATCH 09/10] Do not use is_implicit for now --- runtime/runtime/src/actions.rs | 6 ++++-- tools/fork-network/src/cli.rs | 30 +++++++++++++++++++++--------- tools/mirror/src/genesis.rs | 26 +++++++++++++++++--------- tools/mirror/src/lib.rs | 3 ++- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index c8238d752e3..df4f6e6616e 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -897,7 +897,8 @@ pub(crate) fn check_account_existence( } else { // TODO: this should be `config.implicit_account_creation`. if config.wasm_config.implicit_account_creation - && account_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + && account_id.get_account_type() == AccountType::NearImplicitAccount { // If the account doesn't exist and it's implicit, then you // should only be able to create it using single transfer action. @@ -919,7 +920,8 @@ pub(crate) fn check_account_existence( if account.is_none() { return if config.wasm_config.implicit_account_creation && is_the_only_action - && account_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + && account_id.get_account_type() == AccountType::NearImplicitAccount && !is_refund { // OK. It's implicit account creation. diff --git a/tools/fork-network/src/cli.rs b/tools/fork-network/src/cli.rs index ee5d617d7b1..74ca5f54a65 100644 --- a/tools/fork-network/src/cli.rs +++ b/tools/fork-network/src/cli.rs @@ -8,6 +8,7 @@ use near_epoch_manager::{EpochManager, EpochManagerAdapter, EpochManagerHandle}; use near_mirror::key_mapping::{map_account, map_key}; use near_o11y::default_subscriber_with_opentelemetry; use near_o11y::env_filter::make_env_filter; +use near_primitives::account::id::AccountType; use near_primitives::account::{AccessKey, AccessKeyPermission, Account}; use near_primitives::borsh; use near_primitives::hash::CryptoHash; @@ -486,7 +487,8 @@ impl ForkNetworkCommand { if let Some(sr) = StateRecord::from_raw_key_value(key.clone(), value.clone()) { match sr { StateRecord::AccessKey { account_id, public_key, access_key } => { - if !account_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() != AccountType::NearImplicitAccount && access_key.permission == AccessKeyPermission::FullAccess { has_full_key.insert(account_id.clone()); @@ -503,7 +505,8 @@ impl ForkNetworkCommand { } StateRecord::Account { account_id, account } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { let new_account_id = map_account(&account_id, None); storage_mutator.delete_account(account_id)?; storage_mutator.set_account(new_account_id, account)?; @@ -511,7 +514,8 @@ impl ForkNetworkCommand { } } StateRecord::Data { account_id, data_key, value } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { let new_account_id = map_account(&account_id, None); storage_mutator.delete_data(account_id, &data_key)?; storage_mutator.set_data(new_account_id, &data_key, value)?; @@ -519,7 +523,8 @@ impl ForkNetworkCommand { } } StateRecord::Contract { account_id, code } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { let new_account_id = map_account(&account_id, None); storage_mutator.delete_code(account_id)?; storage_mutator.set_code(new_account_id, code)?; @@ -527,8 +532,11 @@ impl ForkNetworkCommand { } } StateRecord::PostponedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() - || receipt.receiver_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if receipt.predecessor_id.get_account_type() + == AccountType::NearImplicitAccount + || receipt.receiver_id.get_account_type() + == AccountType::NearImplicitAccount { let new_receipt = Receipt { predecessor_id: map_account(&receipt.predecessor_id, None), @@ -542,7 +550,8 @@ impl ForkNetworkCommand { } } StateRecord::ReceivedData { account_id, data_id, data } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { let new_account_id = map_account(&account_id, None); storage_mutator.delete_received_data(account_id, data_id)?; storage_mutator.set_received_data(new_account_id, data_id, &data)?; @@ -550,8 +559,11 @@ impl ForkNetworkCommand { } } StateRecord::DelayedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() - || receipt.receiver_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if receipt.predecessor_id.get_account_type() + == AccountType::NearImplicitAccount + || receipt.receiver_id.get_account_type() + == AccountType::NearImplicitAccount { let new_receipt = Receipt { predecessor_id: map_account(&receipt.predecessor_id, None), diff --git a/tools/mirror/src/genesis.rs b/tools/mirror/src/genesis.rs index 6f354d8df76..38415ac8f5a 100644 --- a/tools/mirror/src/genesis.rs +++ b/tools/mirror/src/genesis.rs @@ -1,4 +1,5 @@ use near_primitives::state_record::StateRecord; +use near_primitives_core::account::id::AccountType; use near_primitives_core::account::{AccessKey, AccessKeyPermission}; use serde::ser::{SerializeSeq, Serializer}; use std::collections::HashSet; @@ -38,7 +39,8 @@ pub fn map_records>( public_key: replacement.public_key(), access_key: access_key.clone(), }; - if !account_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() != AccountType::NearImplicitAccount && access_key.permission == AccessKeyPermission::FullAccess { has_full_key.insert(account_id.clone()); @@ -48,7 +50,8 @@ pub fn map_records>( records_seq.serialize_element(&new_record).unwrap(); } StateRecord::Account { account_id, .. } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } else { accounts.insert(account_id.clone()); @@ -56,20 +59,23 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::Data { account_id, .. } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } records_seq.serialize_element(&r).unwrap(); } StateRecord::Contract { account_id, .. } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } records_seq.serialize_element(&r).unwrap(); } StateRecord::PostponedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() - || receipt.receiver_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if receipt.predecessor_id.get_account_type() == AccountType::NearImplicitAccount + || receipt.receiver_id.get_account_type() == AccountType::NearImplicitAccount { receipt.predecessor_id = crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref()); @@ -79,14 +85,16 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::ReceivedData { account_id, .. } => { - if account_id.get_account_type().is_implicit() { + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if account_id.get_account_type() == AccountType::NearImplicitAccount { *account_id = crate::key_mapping::map_account(&account_id, secret.as_ref()); } records_seq.serialize_element(&r).unwrap(); } StateRecord::DelayedReceipt(receipt) => { - if receipt.predecessor_id.get_account_type().is_implicit() - || receipt.receiver_id.get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if receipt.predecessor_id.get_account_type() == AccountType::NearImplicitAccount + || receipt.receiver_id.get_account_type() == AccountType::NearImplicitAccount { receipt.predecessor_id = crate::key_mapping::map_account(&receipt.predecessor_id, secret.as_ref()); diff --git a/tools/mirror/src/lib.rs b/tools/mirror/src/lib.rs index c09a4b75b33..84f1c3e0c62 100644 --- a/tools/mirror/src/lib.rs +++ b/tools/mirror/src/lib.rs @@ -990,7 +990,8 @@ impl TxMirror { actions.push(Action::DeleteKey(Box::new(DeleteKeyAction { public_key }))); } Action::Transfer(_) => { - if tx.receiver_id().get_account_type().is_implicit() + // TODO(eth-implicit) Change back to is_implicit() when ETH-implicit accounts are supported. + if tx.receiver_id().get_account_type() == AccountType::NearImplicitAccount && source_actions.len() == 1 { let target_account = From 79c1ddda1fb4a9befb91cea39c7742dc8166c93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Chuda=C5=9B?= Date: Mon, 6 Nov 2023 12:32:26 +0100 Subject: [PATCH 10/10] Remove eth_implicit_test_account as it is currently unused --- core/primitives/src/test_utils.rs | 12 ------------ .../src/tests/client/features/delegate_action.rs | 4 +--- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/core/primitives/src/test_utils.rs b/core/primitives/src/test_utils.rs index 327dc1c1524..18d111e127a 100644 --- a/core/primitives/src/test_utils.rs +++ b/core/primitives/src/test_utils.rs @@ -558,8 +558,6 @@ pub fn create_user_test_signer(account_name: &AccountIdRef) -> InMemorySigner { let account_id = account_name.to_owned(); if account_id == near_implicit_test_account() { InMemorySigner::from_secret_key(account_id, near_implicit_test_account_secret()) - } else if account_id == eth_implicit_test_account() { - InMemorySigner::from_secret_key(account_id, eth_implicit_test_account_secret()) } else { InMemorySigner::from_seed(account_id, KeyType::ED25519, account_name.as_str()) } @@ -575,16 +573,6 @@ pub fn near_implicit_test_account_secret() -> SecretKey { "ed25519:5roj6k68kvZu3UEJFyXSfjdKGrodgZUfFLZFpzYXWtESNsLWhYrq3JGi4YpqeVKuw1m9R2TEHjfgWT1fjUqB1DNy".parse().unwrap() } -/// A fixed ETH-implicit account for which tests can know the private key. -pub fn eth_implicit_test_account() -> AccountId { - "0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap() -} - -/// Private key for the fixed ETH-implicit test account. -pub fn eth_implicit_test_account_secret() -> SecretKey { - "secp256k1:X4ETFKtQkSGVoZEnkn7bZ3LyajJaK2b3eweXaKmynGx".parse().unwrap() -} - impl FinalExecutionOutcomeView { #[track_caller] /// Check transaction and all transitive receipts for success status. diff --git a/integration-tests/src/tests/client/features/delegate_action.rs b/integration-tests/src/tests/client/features/delegate_action.rs index 31e47c3fbbf..503b6278541 100644 --- a/integration-tests/src/tests/client/features/delegate_action.rs +++ b/integration-tests/src/tests/client/features/delegate_action.rs @@ -137,9 +137,7 @@ fn check_meta_tx_execution( .nonce; let user_pubk = match sender.get_account_type() { AccountType::NearImplicitAccount => PublicKey::from_near_implicit_account(&sender).unwrap(), - AccountType::EthImplicitAccount => { - PublicKey::from_seed(KeyType::SECP256K1, sender.as_ref()) - } + AccountType::EthImplicitAccount => PublicKey::from_seed(KeyType::ED25519, sender.as_ref()), AccountType::NamedAccount => PublicKey::from_seed(KeyType::ED25519, sender.as_ref()), }; let user_nonce_before = node_user.get_access_key(&sender, &user_pubk).unwrap().nonce;