Skip to content

Commit

Permalink
Wallet Contract placeholder tests
Browse files Browse the repository at this point in the history
  • Loading branch information
staffik committed Nov 21, 2023
1 parent de3e41e commit 3a77c2a
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 23 deletions.
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
5 changes: 4 additions & 1 deletion core/crypto/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ impl PublicKey {
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
5 changes: 5 additions & 0 deletions core/primitives/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,11 @@ pub fn near_implicit_test_account_secret() -> SecretKey {
"ed25519:5roj6k68kvZu3UEJFyXSfjdKGrodgZUfFLZFpzYXWtESNsLWhYrq3JGi4YpqeVKuw1m9R2TEHjfgWT1fjUqB1DNy".parse().unwrap()
}

/// A fixed ETH-implicit account.
pub fn eth_implicit_test_account() -> AccountId {
"0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap()
}

impl FinalExecutionOutcomeView {
#[track_caller]
/// Check transaction and all transitive receipts for success status.
Expand Down
1 change: 1 addition & 0 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ pub fn account_is_implicit(account_id: &AccountId, protocol_version: ProtocolVer
}

/// Derives `AccountId` from `PublicKey`.
/// If the key type is ED25519, returns hex-encoded copy of the key.
/// If the key type is SECP256K1, returns '0x' + keccak256(public_key)[12:32].hex().
pub fn derive_account_id_from_public_key(public_key: &PublicKey) -> AccountId {
match public_key.key_type() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ use near_network::shards_manager::ShardsManagerRequestFromNetwork;
use near_network::types::{NetworkRequests, PeerManagerMessageRequest};
use near_o11y::testonly::init_test_logger;
use near_primitives::account::AccessKey;
use near_primitives::errors::InvalidTxError;
use near_primitives::checked_feature;
use near_primitives::errors::{InvalidAccessKeyError, InvalidTxError};
use near_primitives::runtime::config_store::RuntimeConfigStore;
use near_primitives::shard_layout::ShardLayout;
use near_primitives::sharding::ChunkHash;
use near_primitives::transaction::SignedTransaction;
use near_primitives::transaction::{Action, AddKeyAction, DeployContractAction, 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::utils::{derive_account_id_from_public_key, wallet_contract_placeholder};
use near_primitives::version::{ProtocolFeature, ProtocolVersion, PROTOCOL_VERSION};
use near_primitives::views::FinalExecutionStatus;
use nearcore::config::GenesisExt;
use nearcore::test_utils::TestEnvNightshadeSetupExt;
Expand Down Expand Up @@ -200,7 +201,7 @@ fn get_status_of_tx_hash_collision_for_implicit_account(

/// Test that duplicate transactions from NEAR-implicit accounts are properly rejected.
#[test]
fn test_transaction_hash_collision_for_implicit_account_fail() {
fn test_transaction_hash_collision_for_near_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());
Expand All @@ -216,7 +217,7 @@ 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() {
fn test_transaction_hash_collision_for_near_implicit_account_ok() {
let protocol_version =
ProtocolFeature::AccessKeyNonceForImplicitAccounts.protocol_version() - 1;
let secret_key = SecretKey::from_seed(KeyType::ED25519, "test");
Expand All @@ -231,6 +232,103 @@ fn test_transaction_hash_collision_for_implicit_account_ok() {
);
}

/// Test that transactions from ETH-implicit accounts are rejected.
#[test]
fn test_transaction_from_eth_implicit_account_fail() {
if !checked_feature!("stable", EthImplicit, PROTOCOL_VERSION) {
return;
}
let genesis = Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1);
let mut env = TestEnv::builder(ChainGenesis::test())
.real_epoch_managers(&genesis.config)
.nightshade_runtimes(&genesis)
.build();
let genesis_block = env.clients[0].chain.get_block_by_height(0).unwrap();
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 secret_key = SecretKey::from_seed(KeyType::SECP256K1, "test");
let public_key = secret_key.public_key();
let implicit_account_id = derive_account_id_from_public_key(&public_key);
let implicit_account_signer =
InMemorySigner::from_secret_key(implicit_account_id.clone(), secret_key);

// Send money to ETH-implicit account, invoking its creation.
let send_money_tx = SignedTransaction::send_money(
1,
"test1".parse().unwrap(),
implicit_account_id.clone(),
&signer1,
deposit_for_account_creation,
*genesis_block.hash(),
);
// Check for tx success status and get new block height.
height = check_tx_processing(&mut env, send_money_tx, height, blocks_number);
let block = env.clients[0].chain.get_block_by_height(height - 1).unwrap();

// Try to send money from ETH-implicit account using `(block_height - 1) * 1e6` as a nonce.
// That would be a good nonce for any access key, but the transaction should fail nonetheless because there is no access key.
let nonce = (height - 1) * AccessKey::ACCESS_KEY_NONCE_RANGE_MULTIPLIER;
let send_money_from_implicit_account_tx = SignedTransaction::send_money(
nonce,
implicit_account_id.clone(),
"test0".parse().unwrap(),
&implicit_account_signer,
100,
*block.hash(),
);
let response = env.clients[0].process_tx(send_money_from_implicit_account_tx, false, false);
let expected_tx_error = ProcessTxResponse::InvalidTx(InvalidTxError::InvalidAccessKeyError(
InvalidAccessKeyError::AccessKeyNotFound {
account_id: implicit_account_id.clone(),
public_key: public_key.clone(),
},
));
assert_eq!(response, expected_tx_error);

// Try to delete ETH-implicit account.
let delete_implicit_account_tx = SignedTransaction::delete_account(
nonce,
implicit_account_id.clone(),
implicit_account_id.clone(),
"test0".parse().unwrap(),
&implicit_account_signer,
*block.hash(),
);
let response = env.clients[0].process_tx(delete_implicit_account_tx, false, false);
assert_eq!(response, expected_tx_error);

// Try to add an access key to the ETH-implicit account.
let add_access_key_to_implicit_account_tx = SignedTransaction::from_actions(
nonce,
implicit_account_id.clone(),
implicit_account_id.clone(),
&implicit_account_signer,
vec![Action::AddKey(Box::new(AddKeyAction {
public_key,
access_key: AccessKey::full_access(),
}))],
*block.hash(),
);
let response = env.clients[0].process_tx(add_access_key_to_implicit_account_tx, false, false);
assert_eq!(response, expected_tx_error);

// Try to deploy the Wallet Contract again to the ETH-implicit account.
let wallet_contract_code = wallet_contract_placeholder().code().to_vec();
let add_access_key_to_implicit_account_tx = SignedTransaction::from_actions(
nonce,
implicit_account_id.clone(),
implicit_account_id,
&implicit_account_signer,
vec![Action::DeployContract(DeployContractAction { code: wallet_contract_code })],
*block.hash(),
);
let response = env.clients[0].process_tx(add_access_key_to_implicit_account_tx, false, false);
assert_eq!(response, expected_tx_error);
}

/// Test that chunks with transactions that have expired are considered invalid.
#[test]
fn test_chunk_transaction_validity() {
Expand Down
79 changes: 64 additions & 15 deletions integration-tests/src/tests/client/features/delegate_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ use near_crypto::{KeyType, PublicKey, Signer};
use near_primitives::account::{
id::AccountType, AccessKey, AccessKeyPermission, FunctionCallPermission,
};
use near_primitives::checked_feature;
use near_primitives::config::ActionCosts;
use near_primitives::errors::{
ActionError, ActionErrorKind, ActionsValidationError, InvalidAccessKeyError, InvalidTxError,
TxExecutionError,
};
use near_primitives::test_utils::{create_user_test_signer, near_implicit_test_account};
use near_primitives::test_utils::{
create_user_test_signer, eth_implicit_test_account, near_implicit_test_account,
};
use near_primitives::transaction::{
Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
};
use near_primitives::types::{AccountId, Balance};
use near_primitives::version::{ProtocolFeature, ProtocolVersion};
use near_primitives::version::{ProtocolFeature, ProtocolVersion, PROTOCOL_VERSION};
use near_primitives::views::{
AccessKeyPermissionView, ExecutionStatusView, FinalExecutionOutcomeView, FinalExecutionStatus,
};
Expand Down Expand Up @@ -121,6 +124,7 @@ fn check_meta_tx_execution(
receiver: AccountId,
) -> (FinalExecutionOutcomeView, i128, i128, i128) {
let node_user = node.user();
let protocol_version = node.genesis().config.protocol_version;

assert_eq!(
relayer,
Expand All @@ -137,7 +141,16 @@ 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::ED25519, sender.as_ref()),
AccountType::EthImplicitAccount => {
if checked_feature!("stable", EthImplicit, protocol_version) {
// Eth-implicit accounts must not have access key.
assert!(node_user.is_locked(&sender).unwrap());
// Panicking because no transaction can be made from this account (no access keys).
panic!("No access keys");
} else {
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;
Expand Down Expand Up @@ -803,12 +816,22 @@ fn meta_tx_create_near_implicit_account_fails() {
meta_tx_create_implicit_account_fails(near_implicit_test_account());
}

#[test]
fn meta_tx_create_eth_implicit_account_fails() {
if !checked_feature!("stable", EthImplicit, PROTOCOL_VERSION) {
return;
}
meta_tx_create_implicit_account_fails(eth_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.
/// In case of Near-implicit accounts it only works with accounts that already exist
/// because it needs to do a nonce check against the access key,
/// which can only exist if the account exists.
/// In case of Eth-implicit accounts the access key does not exist anyway.
fn meta_tx_create_and_use_implicit_account(new_account: AccountId) {
let relayer = bob_account();
let sender = alice_account();
Expand Down Expand Up @@ -840,12 +863,24 @@ 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
#[test]
fn meta_tx_create_and_use_eth_implicit_account() {
if !checked_feature!("stable", EthImplicit, PROTOCOL_VERSION) {
return;
}
meta_tx_create_and_use_implicit_account(eth_implicit_test_account());
}

/// Creating an implicit account with a meta tx transfer and try using 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.
///
/// Using the account should only work for Near-implicit accounts,
/// as Eth-implicit accounts do not have access keys
/// and they can only be used by calling associated smart contract.
fn meta_tx_create_implicit_account(new_account: AccountId) {
let relayer = bob_account();
let sender = alice_account();
Expand All @@ -860,7 +895,7 @@ 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 near-implicit"),
AccountType::EthImplicitAccount => fee_helper.create_account_transfer_cost(),
AccountType::NamedAccount => panic!("must be near-implicit"),
};
check_meta_tx_no_fn_call(
Expand All @@ -882,24 +917,38 @@ fn meta_tx_create_implicit_account(new_account: AccountId) {
let transfer_amount = initial_amount / 2;
let actions = vec![Action::Transfer(TransferAction { deposit: transfer_amount })];
let tx_cost = fee_helper.transfer_cost();
check_meta_tx_no_fn_call(
let tx_outcome = check_meta_tx_no_fn_call(
&node,
actions,
tx_cost,
transfer_amount,
new_account.clone(),
relayer,
sender,
)
.assert_success();

// balance of the new account should NOT change, the relayer pays for it!
// (note: relayer balance checks etc are done in the shared checker function)
let balance = node.view_balance(&new_account).expect("failed looking up balance");
assert_eq!(balance, initial_amount);
);
match new_account.get_account_type() {
AccountType::NearImplicitAccount => {
tx_outcome.assert_success();
// balance of the new account should NOT change, the relayer pays for it!
// (note: relayer balance checks etc are done in the shared checker function)
let balance = node.view_balance(&new_account).expect("failed looking up balance");
assert_eq!(balance, initial_amount);
}
AccountType::EthImplicitAccount => tx_outcome.assert_success(),
AccountType::NamedAccount => panic!("must be near-implicit"),
};
}

#[test]
fn meta_tx_create_near_implicit_account() {
meta_tx_create_implicit_account(near_implicit_test_account());
}

#[test]
#[should_panic(expected = "No access keys")]
fn meta_tx_create_eth_implicit_account() {
if !checked_feature!("stable", EthImplicit, PROTOCOL_VERSION) {
return;
}
meta_tx_create_implicit_account(eth_implicit_test_account());
}
2 changes: 1 addition & 1 deletion integration-tests/src/tests/standard_cases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ pub fn transfer_tokens_implicit_account(node: impl Node, public_key: PublicKey)
}
AccountType::EthImplicitAccount => {
// A transfer to ETH-implicit address does not create access key.
assert!(node_user.get_access_key(&receiver_id, &public_key).is_err());
assert!(view_access_key.is_err());
}
AccountType::NamedAccount => std::panic!("must be implicit"),
}
Expand Down
3 changes: 3 additions & 0 deletions integration-tests/src/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub trait User {

fn view_state(&self, account_id: &AccountId, prefix: &[u8]) -> Result<ViewStateResult, String>;

/// Returns whether the account is locked (has no access keys).
fn is_locked(&self, account_id: &AccountId) -> Result<bool, String>;

fn view_call(
&self,
account_id: &AccountId,
Expand Down
10 changes: 10 additions & 0 deletions integration-tests/src/user/rpc_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ impl User for RpcUser {
}
}

fn is_locked(&self, account_id: &AccountId) -> Result<bool, String> {
let query = QueryRequest::ViewAccessKeyList { account_id: account_id.clone() };
match self.query(query)?.kind {
near_jsonrpc_primitives::types::query::QueryResponseKind::AccessKeyList(
access_keys,
) => Ok(access_keys.keys.is_empty()),
_ => Err("Invalid type of response".into()),
}
}

fn view_contract_code(&self, account_id: &AccountId) -> Result<ContractCodeView, String> {
let query = QueryRequest::ViewCode { account_id: account_id.clone() };
match self.query(query)?.kind {
Expand Down
8 changes: 8 additions & 0 deletions integration-tests/src/user/runtime_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ impl User for RuntimeUser {
.map_err(|err| err.to_string())
}

fn is_locked(&self, account_id: &AccountId) -> Result<bool, String> {
let state_update = self.client.read().expect(POISONED_LOCK_ERR).get_state_update();
self.trie_viewer
.view_access_keys(&state_update, account_id)
.map(|access_keys| access_keys.is_empty())
.map_err(|err| err.to_string())
}

fn view_call(
&self,
account_id: &AccountId,
Expand Down

0 comments on commit 3a77c2a

Please sign in to comment.