diff --git a/Cargo.lock b/Cargo.lock index 494c7f2d04a..a6220e9c310 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3350,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/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..a2b7bf5acd5 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,49 @@ 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 { - 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() +pub fn transfer_exec_fee( + cfg: &RuntimeFeesConfig, + implicit_account_creation_allowed: bool, + receiver_account_type: AccountType, +) -> Gas { + let transfer_fee = cfg.fee(ActionCosts::transfer).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. + (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 + + cfg.fee(ActionCosts::create_account).exec_fee() + + cfg.fee(ActionCosts::add_full_access_key).exec_fee() + } } } 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 { - 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) + let transfer_fee = cfg.fee(ActionCosts::transfer).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. + (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 + + cfg.fee(ActionCosts::create_account).send_fee(sender_is_receiver) + + cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver) + } } } diff --git a/core/primitives/src/test_utils.rs b/core/primitives/src/test_utils.rs index bb35526869b..18d111e127a 100644 --- a/core/primitives/src/test_utils.rs +++ b/core/primitives/src/test_utils.rs @@ -555,21 +555,21 @@ 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 { 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() } diff --git a/core/primitives/src/utils.rs b/core/primitives/src/utils.rs index 749320f7e33..06894bb9eaf 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::ED25519PublicKey; +use near_primitives_core::account::id::AccountId; + use std::mem::size_of; use std::ops::Deref; @@ -465,9 +469,25 @@ where Serializable(object) } +/// 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: &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(); + let account_id = derive_near_implicit_account_id(public_key.unwrap_as_ed25519()); + assert_eq!(account_id, expected); + } #[test] fn test_num_chunk_producers() { 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..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 @@ -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_near_implicit_account_id; 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,37 @@ 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_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(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 secret_key = SecretKey::from_seed(KeyType::ED25519, "test"); + 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(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..503b6278541 100644 --- a/integration-tests/src/tests/client/features/delegate_action.rs +++ b/integration-tests/src/tests/client/features/delegate_action.rs @@ -9,13 +9,15 @@ 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::{ + id::AccountType, AccessKey, AccessKeyPermission, FunctionCallPermission, +}; 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 +135,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::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; @@ -779,11 +781,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 +798,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 +835,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 +857,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 near-implicit"), + AccountType::NamedAccount => panic!("must be near-implicit"), + }; check_meta_tx_no_fn_call( &node, actions, @@ -887,3 +898,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..4a47ec2c397 100644 --- a/integration-tests/src/tests/standard_cases/mod.rs +++ b/integration-tests/src/tests/standard_cases/mod.rs @@ -4,9 +4,11 @@ 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::{ + id::AccountType, AccessKey, AccessKeyPermission, FunctionCallPermission, +}; use near_primitives::config::{ActionCosts, ExtCosts}; use near_primitives::errors::{ ActionError, ActionErrorKind, FunctionCallError, InvalidAccessKeyError, InvalidTxError, @@ -14,6 +16,7 @@ use near_primitives::errors::{ }; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::types::{AccountId, Balance, TrieNodesCount}; +use near_primitives::utils::derive_near_implicit_account_id; use near_primitives::views::{ AccessKeyView, AccountView, ExecutionMetadataView, FinalExecutionOutcomeView, FinalExecutionStatus, @@ -327,16 +330,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_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 near-implicit"), + AccountType::NamedAccount => std::panic!("must be near-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 +364,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 near-implicit"), + AccountType::NamedAccount => std::panic!("must be near-implicit"), + } let transaction_result = node_user.send_money(account_id.clone(), receiver_id.clone(), tokens_used).unwrap(); @@ -382,16 +395,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_near_implicit_account_id(public_key.unwrap_as_ed25519()); let transaction_result = node_user .create_account( @@ -402,14 +412,21 @@ 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) - + fee_helper - .cfg() - .fee(near_primitives::config::ActionCosts::add_full_access_key) - .send_fee(false), - ); + let cost = match receiver_id.get_account_type() { + AccountType::NearImplicitAccount => { + 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 near-implicit"), + AccountType::NamedAccount => std::panic!("must be near-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..6288ee8e5a0 100644 --- a/runtime/near-vm-runner/src/logic/logic.rs +++ b/runtime/near-vm-runner/src/logic/logic.rs @@ -1772,10 +1772,17 @@ 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 send_fee = transfer_send_fee( + self.fees_config, + sir, + self.config.implicit_account_creation, + receiver_id.get_account_type(), + ); + let exec_fee = transfer_exec_fee( + self.fees_config, + self.config.implicit_account_creation, + 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..df4f6e6616e 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, @@ -428,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, @@ -440,30 +442,41 @@ 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; - } + 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; + } - // 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(); + // 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( - 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, - )); + *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); + set_access_key(state_update, account_id.clone(), public_key, &access_key); + } + // 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 near-implicit") + } + } } pub(crate) fn action_deploy_contract( @@ -883,14 +896,17 @@ 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 + // 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. // 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 +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.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/runtime/runtime/src/config.rs b/runtime/runtime/src/config.rs index cf6f6cc2e99..37d9e3627e5 100644 --- a/runtime/runtime/src/config.rs +++ b/runtime/runtime/src/config.rs @@ -96,9 +96,12 @@ pub fn total_send_fees( } Transfer(_) => { // Account for implicit account creation - let is_receiver_implicit = - config.wasm_config.implicit_account_creation && receiver_id.is_implicit(); - transfer_send_fee(fees, sender_is_receiver, is_receiver_implicit) + transfer_send_fee( + fees, + sender_is_receiver, + config.wasm_config.implicit_account_creation, + receiver_id.get_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 +191,11 @@ pub fn exec_fee(config: &RuntimeConfig, action: &Action, receiver_id: &AccountId } Transfer(_) => { // Account for implicit account creation - let is_receiver_implicit = - config.wasm_config.implicit_account_creation && receiver_id.is_implicit(); - transfer_exec_fee(fees, is_receiver_implicit) + 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/fork-network/src/cli.rs b/tools/fork-network/src/cli.rs index 27a766d464f..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.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.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.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.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,7 +532,11 @@ impl ForkNetworkCommand { } } StateRecord::PostponedReceipt(receipt) => { - if receipt.predecessor_id.is_implicit() || receipt.receiver_id.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), @@ -541,7 +550,8 @@ impl ForkNetworkCommand { } } StateRecord::ReceivedData { account_id, data_id, data } => { - if account_id.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)?; @@ -549,7 +559,11 @@ impl ForkNetworkCommand { } } StateRecord::DelayedReceipt(receipt) => { - if receipt.predecessor_id.is_implicit() || receipt.receiver_id.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 892b4a9eee0..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.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.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,19 +59,24 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::Data { account_id, .. } => { - if account_id.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.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.is_implicit() || receipt.receiver_id.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()); receipt.receiver_id = @@ -77,13 +85,17 @@ pub fn map_records>( records_seq.serialize_element(&r).unwrap(); } StateRecord::ReceivedData { account_id, .. } => { - if account_id.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.is_implicit() || receipt.receiver_id.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()); receipt.receiver_id = diff --git a/tools/mirror/src/key_mapping.rs b/tools/mirror/src/key_mapping.rs index 7c5b2d1989c..4cbc8608419 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::types::AccountId; +use near_primitives::utils::derive_near_implicit_account_id; +use near_primitives_core::account::id::AccountType; 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 near-implicit"); + let mapped_key = map_key(&public_key, secret); + 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 97ad65edb64..84f1c3e0c62 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,10 @@ impl TxMirror { actions.push(Action::DeleteKey(Box::new(DeleteKeyAction { public_key }))); } Action::Transfer(_) => { - if tx.receiver_id().is_implicit() && source_actions.len() == 1 { + // 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 = crate::key_mapping::map_account(tx.receiver_id(), self.secret.as_ref()); if !account_exists(&self.target_view_client, &target_account) @@ -1001,9 +1005,13 @@ 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 near-implicit"); + nonce_updates.insert((target_account, public_key)); + } } } actions.push(action.clone());