Skip to content

Commit

Permalink
Add corresponding ETH-implicit account tests
Browse files Browse the repository at this point in the history
  • Loading branch information
staffik committed Oct 26, 2023
1 parent 4797eec commit bea1220
Show file tree
Hide file tree
Showing 31 changed files with 445 additions and 187 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion chain/rosetta-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ async fn construction_derive(
let public_key: near_crypto::PublicKey = (&public_key)
.try_into()
.map_err(|_| errors::ErrorKind::InvalidInput("Invalid PublicKey".to_string()))?;
// TODO should ETH-implicit account be handled there?
// TODO Rosetta-RPC: should we handle ETH-implicit accounts here?
let address = if let near_crypto::KeyType::ED25519 = public_key.key_type() {
hex::encode(public_key.key_data())
} else {
Expand Down
67 changes: 56 additions & 11 deletions core/account-id/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,13 @@ impl AccountId {
/// assert!(!alice_app.is_sub_account_of(&near_tla));
/// ```
pub fn is_sub_account_of(&self, parent: &AccountId) -> bool {
// TODO what if this is EthImplicitAccount ?
// TODO Is it ok that an account could be a sub account of an EthImplicitAccount ?
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, possibly with capital letters.
/// 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).
///
Expand All @@ -161,15 +161,15 @@ impl AccountId {
/// let alice: AccountId = "alice.near".parse().unwrap();
/// assert!(!alice.is_eth_implicit());
///
/// let rando = "0xb794f5eA0ba39494ce839613FFfba74279579268"
/// let rando = "0xb794f5ea0ba39494ce839613fffba74279579268"
/// .parse::<AccountId>()
/// .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'A'..=b'F' | b'0'..=b'9'))
&& 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.
Expand Down Expand Up @@ -475,7 +475,7 @@ mod tests {
"b-o_w_e-n",
"no_lols",
"0123456789012345678901234567890123456789012345678901234567890123",
"0xb794f5eA0ba39494ce839613FFfba74279579268",
"0xb794f5ea0ba39494ce839613fffba74279579268",
// Valid, but can't be created
"near.a",
];
Expand Down Expand Up @@ -591,6 +591,7 @@ mod tests {
"alex-skidanov",
"b-o_w_e-n",
"no_lols",
"0xb794f5ea0ba39494ce839613fffba74279579268",
"0123456789012345678901234567890123456789012345678901234567890123",
];
for account_id in ok_top_level_account_ids {
Expand Down Expand Up @@ -715,6 +716,10 @@ mod tests {
"123456789012345678901234567890123456789012345678901234567890",
"1234567890.123456789012345678901234567890123456789012345678901234567890",
),
(
"b794f5ea0ba39494ce839613fffba74279579268",
"0xb794f5ea0ba39494ce839613fffba74279579268",
),
("aa", "ъ@aa"),
("aa", "ъ.aa"),
];
Expand All @@ -731,17 +736,16 @@ mod tests {
}
}

// TODO add corresponding test for 42 len hex ETH accounts
#[test]
fn test_is_account_id_64_len_hex() {
let valid_64_len_hex_account_ids = &[
fn test_is_account_id_near_implicit() {
let valid_near_implicit_account_ids = &[
"0000000000000000000000000000000000000000000000000000000000000000",
"6174617461746174617461746174617461746174617461746174617461746174",
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"20782e20662e64666420482123494b6b6c677573646b6c66676a646b6c736667",
];
for valid_account_id in valid_64_len_hex_account_ids {
for valid_account_id in valid_near_implicit_account_ids {
assert!(
matches!(
valid_account_id.parse::<AccountId>(),
Expand All @@ -752,15 +756,15 @@ mod tests {
);
}

let invalid_64_len_hex_account_ids = &[
let invalid_near_implicit_account_ids = &[
"000000000000000000000000000000000000000000000000000000000000000",
"6.74617461746174617461746174617461746174617461746174617461746174",
"012-456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"fffff_ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo",
"00000000000000000000000000000000000000000000000000000000000000",
];
for invalid_account_id in invalid_64_len_hex_account_ids {
for invalid_account_id in invalid_near_implicit_account_ids {
assert!(
!matches!(
invalid_account_id.parse::<AccountId>(),
Expand All @@ -772,6 +776,47 @@ mod tests {
}
}

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

let invalid_eth_implicit_account_ids = &[
"04b794f5ea0ba39494ce839613fffba74279579268",
"0x000000000000000000000000000000000000000",
"0x6.74617461746174617461746174617461746174",
"0x012-456789abcdef0123456789abcdef01234567",
"0xfffff_ffffffffffffffffffffffffffffffffff",
"0xoooooooooooooooooooooooooooooooooooooooo",
"0x00000000000000000000000000000000000000000",
];
for invalid_account_id in invalid_eth_implicit_account_ids {
assert!(
!matches!(
invalid_account_id.parse::<AccountId>(),
Ok(account_id) if account_id.get_account_type() == AccountType::EthImplicitAccount
),
"Account ID {} is not an implicit account",
invalid_account_id
);
}
}

#[test]
#[cfg(feature = "arbitrary")]
fn test_arbitrary() {
Expand Down
7 changes: 7 additions & 0 deletions core/crypto/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ impl PublicKey {
Self::SECP256K1(_) => panic!(),
}
}

pub fn unwrap_as_secp256k1(&self) -> &Secp256K1PublicKey {
match self {
Self::SECP256K1(key) => key,
Self::ED25519(_) => panic!(),
}
}
}

// This `Hash` implementation is safe since it retains the property
Expand Down
7 changes: 5 additions & 2 deletions core/crypto/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ impl PublicKey {
KeyType::ED25519 => {
let keypair = ed25519_key_pair_from_seed(seed);
PublicKey::ED25519(ED25519PublicKey(keypair.public.to_bytes()))
}
_ => unimplemented!(),
},
KeyType::SECP256K1 => {
let secret_key = SecretKey::SECP256K1(secp256k1_secret_key_from_seed(seed));
PublicKey::SECP256K1(secret_key.public_key().unwrap_as_secp256k1().clone())
},
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/primitives-core/src/runtime/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ pub fn transfer_exec_fee(
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::EthImplicitAccount {
if receiver_account_type == AccountType::NearImplicitAccount {
result += cfg.fee(ActionCosts::add_full_access_key).exec_fee();
}
}
Expand All @@ -230,7 +230,7 @@ pub fn transfer_send_fee(
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::EthImplicitAccount {
if receiver_account_type == AccountType::NearImplicitAccount {
result += cfg.fee(ActionCosts::add_full_access_key).send_fee(sender_is_receiver);
}
}
Expand Down
1 change: 1 addition & 0 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
serde_yaml.workspace = true
sha3.workspace = true
smart-default.workspace = true
stdx.workspace = true
strum.workspace = true
Expand Down
6 changes: 3 additions & 3 deletions core/primitives/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ pub enum InvalidAccessKeyError {
},
/// Having a deposit with a function call action is not allowed with a function call access key.
DepositWithFunctionCall,
/// Transaction is from ETH-implicit `account_id` and uses invalid `public_key` for that address.
/// ETH-implicit `account_id` isn't derived from the `public_key`.
InvalidPkForEthAddress { account_id: AccountId, public_key: PublicKey },
}

Expand Down Expand Up @@ -487,7 +487,7 @@ pub enum ActionErrorKind {
/// receipt validation.
NewReceiptValidationError(ReceiptValidationError),
/// Error occurs when a `CreateAccount` action is called on hex-characters
/// account of length 64 or 42. See implicit account creation NEP:
/// account of length 64 or 40 (when prefixed with '0x'). See implicit account creation NEP:
/// <https://github.com/nearprotocol/NEPs/pull/71>.
///
/// TODO(#8598): This error is named very poorly. A better name would be
Expand Down Expand Up @@ -614,7 +614,7 @@ impl Display for InvalidAccessKeyError {
},
InvalidAccessKeyError::InvalidPkForEthAddress { account_id, public_key } => write!(
f,
"Address {:?} is ETH-implicit and does not correspond to the public_key {}",
"ETH-implicit address {:?} isn't derived from the public_key {}",
account_id, public_key
),
}
Expand Down
23 changes: 16 additions & 7 deletions core/primitives/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,26 +555,35 @@ pub fn create_test_signer(account_name: &str) -> InMemoryValidatorSigner {
/// Should be used only in tests.
pub fn create_user_test_signer(account_name: &str) -> InMemorySigner {
let account_id = account_name.parse().unwrap();
// TODO add support for ETH-implicit test account
if account_id == near_implicit_test_account() {
InMemorySigner::from_secret_key(account_id, implicit_test_account_secret())
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)
}
}

// TODO add eth_implicit_test_account

/// A fixed implicit account for which tests can know the private key.
/// 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.
Expand Down
31 changes: 31 additions & 0 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -465,10 +469,37 @@ where
Serializable(object)
}

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::SECP256K1 => {
use sha3::Digest;
let pk_hash = sha3::Keccak256::digest(&public_key.key_data());
format!("0x{}", hex::encode(&pk_hash[12..32])).parse().unwrap()
},
}
}

#[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_derive_account_id_from_secp256k1_public_key() {
let public_key = PublicKey::from_seed(KeyType::SECP256K1, "test");
let expected: AccountId = "0x96791e923f8cf697ad9c3290f2c9059f0231b24c".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 {
Expand Down
Loading

0 comments on commit bea1220

Please sign in to comment.