Skip to content

Commit

Permalink
Wallet Contract placeholder (#10269)
Browse files Browse the repository at this point in the history
## Context
NEP: near/NEPs#518
Tracking issue: #10018.

### Goal
We want the NEAR Protocol's ecosystem to be closer with Ethereum by
integrating Web3 wallet support.
To accomplish that, some changes on the protocol level are needed with
an emphasis on user experience continuity.
Following the design, the main change on the protocol level would be to
have ETH-style addresses managed by a new `Wallet Contract` integrated
into the protocol. For seamless onboarding of EVM users, the contract
will be free of charge.

### Previous work
This PR is built on top of two PRs:
* #10020: no-op PR, laying
groundwork for further changes.
* #10224: empty contract as a
placeholder, literally deployed to new ETH accounts.

## Summary
This PR adds `near-wallet-contract` crate (based on
`runtime/near-test-contracts`) that exposes `Wallet Contract` WASM code
to the runtime.
It stops deploying a copy of the `Wallet Contract` to each newly created
ETH-implicit account.
Instead, it uses an in-memory cached contract code on
`execute_function_call` action from such account.

### Changes
- Add `wallet-contract` crate (separated from the workspace) with
placeholder `Wallet Contract` implementation.
- Add `near-wallet-contract` crate (part of the workspace) that
generates (through `build.rs`) and exposes the `Wallet Contract` WASM
code.
- Do not literally deploy `Wallet Contract` when creating ETH-implicit
account. Just store reference to the `Wallet Contract`.
- Treat ETH-implicit accounts specially when retrieving contract code
from an account (in such case returns in-memory cached `Wallet Contract`
code).
- Add tests calling `Wallet Contract` where a transfer from an
ETH-implicit account is possible depending on the public key passed with
`rlp_transaction` argument.
  • Loading branch information
staffik authored Dec 18, 2023
1 parent b8fc11a commit 68c190d
Show file tree
Hide file tree
Showing 22 changed files with 2,226 additions and 136 deletions.
23 changes: 22 additions & 1 deletion Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ members = [
"runtime/near-vm/compiler-test-derive",
"runtime/near-vm-runner",
"runtime/near-vm-runner/fuzz",
"runtime/near-wallet-contract",
"runtime/runtime",
"runtime/runtime-params-estimator",
"runtime/runtime-params-estimator/estimator-warehouse",
Expand Down Expand Up @@ -246,6 +247,7 @@ near-vm-test-generator = { path = "runtime/near-vm/test-generator" }
near-vm-types = { path = "runtime/near-vm/types" }
near-vm-vm = { path = "runtime/near-vm/vm" }
near-vm-wast = { path = "runtime/near-vm/wast" }
near-wallet-contract = { path = "runtime/near-wallet-contract" }
nix = "0.24"
node-runtime = { path = "runtime/runtime" }
num-bigint = "0.3"
Expand Down Expand Up @@ -287,6 +289,7 @@ reqwest = { version = "0.11.14", features = ["blocking"] }
ripemd = "0.1.1"
rkyv = "0.7.31"
rlimit = "0.7"
rlp = "0.5.2"
rocksdb = { version = "0.21.0", default-features = false, features = ["snappy", "lz4", "zstd", "zlib", "jemalloc"] }
runtime-tester = { path = "test-utils/runtime-tester" }
rusqlite = { version = "0.29.0", features = ["bundled", "chrono", "functions"] }
Expand Down
1 change: 0 additions & 1 deletion core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ strum.workspace = true
thiserror.workspace = true
time.workspace = true
tracing.workspace = true
wat.workspace = true

near-crypto.workspace = true
near-fmt.workspace = true
Expand Down
7 changes: 0 additions & 7 deletions core/primitives/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::version::{

use near_crypto::{ED25519PublicKey, Secp256K1PublicKey};
use near_primitives_core::account::id::{AccountId, AccountType};
use near_vm_runner::ContractCode;

use std::mem::size_of;
use std::ops::Deref;
Expand Down Expand Up @@ -471,12 +470,6 @@ where
Serializable(object)
}

// TODO(eth-implicit) Replace this function (and wat dependency) with a real Wallet Contract implementation.
pub fn wallet_contract_placeholder() -> ContractCode {
let code = wat::parse_str(r#"(module (func (export "main")))"#);
ContractCode::new(code.unwrap().to_vec(), None)
}

/// From `near-account-id` version `1.0.0-alpha.2`, `is_implicit` returns true for ETH-implicit accounts.
/// This function is a wrapper for `is_implicit` method so that we can easily differentiate its behavior
/// based on whether ETH-implicit accounts are enabled.
Expand Down
4 changes: 4 additions & 0 deletions integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ once_cell.workspace = true
parking_lot.workspace = true
primitive-types.workspace = true
rand.workspace = true
rlp.workspace = true
serde.workspace = true
serde_json.workspace = true
smart-default.workspace = true
Expand Down Expand Up @@ -58,6 +59,7 @@ near-test-contracts.workspace = true
near-performance-metrics.workspace = true
near-undo-block.workspace = true
near-vm-runner.workspace = true
near-wallet-contract.workspace = true
nearcore.workspace = true
node-runtime.workspace = true
testlib.workspace = true
Expand Down Expand Up @@ -105,6 +107,7 @@ nightly = [
"near-telemetry/nightly",
"near-undo-block/nightly",
"near-vm-runner/nightly",
"near-wallet-contract/nightly",
"nearcore/nightly",
"node-runtime/nightly",
"testlib/nightly",
Expand All @@ -130,6 +133,7 @@ nightly_protocol = [
"near-telemetry/nightly_protocol",
"near-undo-block/nightly_protocol",
"near-vm-runner/nightly_protocol",
"near-wallet-contract/nightly_protocol",
"nearcore/nightly_protocol",
"node-runtime/nightly_protocol",
"testlib/nightly_protocol",
Expand Down
1 change: 1 addition & 0 deletions integration-tests/src/tests/client/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ mod lower_storage_key_limit;
mod nearvm;
mod restore_receipts_after_fix_apply_chunks;
mod restrict_tla;
mod wallet_contract;
mod zero_balance_account;
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@ 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::checked_feature;
use near_primitives::errors::{InvalidAccessKeyError, InvalidTxError};
use near_primitives::errors::InvalidTxError;
use near_primitives::runtime::config_store::RuntimeConfigStore;
use near_primitives::shard_layout::ShardLayout;
use near_primitives::sharding::ChunkHash;
use near_primitives::transaction::{Action, AddKeyAction, DeployContractAction, SignedTransaction};
use near_primitives::transaction::SignedTransaction;
use near_primitives::types::{AccountId, BlockHeight};
use near_primitives::utils::{
derive_eth_implicit_account_id, derive_near_implicit_account_id, wallet_contract_placeholder,
};
use near_primitives::version::{ProtocolFeature, ProtocolVersion, PROTOCOL_VERSION};
use near_primitives::utils::derive_near_implicit_account_id;
use near_primitives::version::{ProtocolFeature, ProtocolVersion};
use near_primitives::views::FinalExecutionStatus;
use nearcore::config::GenesisExt;
use nearcore::test_utils::TestEnvNightshadeSetupExt;
Expand Down Expand Up @@ -239,105 +236,6 @@ fn test_transaction_hash_collision_for_near_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", EthImplicitAccounts, 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 eth_implicit_account_id = derive_eth_implicit_account_id(public_key.unwrap_as_secp256k1());
let eth_implicit_account_signer =
InMemorySigner::from_secret_key(eth_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(),
eth_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_eth_implicit_account_tx = SignedTransaction::send_money(
nonce,
eth_implicit_account_id.clone(),
"test0".parse().unwrap(),
&eth_implicit_account_signer,
100,
*block.hash(),
);
let response = env.clients[0].process_tx(send_money_from_eth_implicit_account_tx, false, false);
let expected_tx_error = ProcessTxResponse::InvalidTx(InvalidTxError::InvalidAccessKeyError(
InvalidAccessKeyError::AccessKeyNotFound {
account_id: eth_implicit_account_id.clone(),
public_key: public_key.clone(),
},
));
assert_eq!(response, expected_tx_error);

// Try to delete ETH-implicit account. Should fail because there is no access key.
let delete_eth_implicit_account_tx = SignedTransaction::delete_account(
nonce,
eth_implicit_account_id.clone(),
eth_implicit_account_id.clone(),
"test0".parse().unwrap(),
&eth_implicit_account_signer,
*block.hash(),
);
let response = env.clients[0].process_tx(delete_eth_implicit_account_tx, false, false);
assert_eq!(response, expected_tx_error);

// Try to add an access key to the ETH-implicit account. Should fail because there is no access key.
let add_access_key_to_eth_implicit_account_tx = SignedTransaction::from_actions(
nonce,
eth_implicit_account_id.clone(),
eth_implicit_account_id.clone(),
&eth_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_eth_implicit_account_tx, false, false);
assert_eq!(response, expected_tx_error);

// Try to deploy the Wallet Contract again to the ETH-implicit account. Should fail because there is no access key.
let wallet_contract_code = wallet_contract_placeholder().code().to_vec();
let add_access_key_to_eth_implicit_account_tx = SignedTransaction::from_actions(
nonce,
eth_implicit_account_id.clone(),
eth_implicit_account_id,
&eth_implicit_account_signer,
vec![Action::DeployContract(DeployContractAction { code: wallet_contract_code })],
*block.hash(),
);
let response =
env.clients[0].process_tx(add_access_key_to_eth_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
11 changes: 8 additions & 3 deletions integration-tests/src/tests/client/features/delegate_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ fn meta_tx_near_transfer() {
let node = RuntimeNode::new(&relayer);
let fee_helper = fee_helper(&node);

let amount = nearcore::NEAR_BASE;
let amount = NEAR_BASE;
let actions = vec![Action::Transfer(TransferAction { deposit: amount })];
let tx_cost = fee_helper.transfer_cost();
check_meta_tx_no_fn_call(&node, actions, tx_cost, amount, sender, relayer, receiver);
Expand Down Expand Up @@ -837,7 +837,7 @@ fn meta_tx_create_and_use_implicit_account(new_account: AccountId) {
// Check the account doesn't exist, yet. We will attempt creating it.
node.view_account(&new_account).expect_err("account already exists");

let initial_amount = nearcore::NEAR_BASE;
let initial_amount = NEAR_BASE;
let actions = vec![
Action::Transfer(TransferAction { deposit: initial_amount }),
Action::DeployContract(DeployContractAction { code: ft_contract().to_vec() }),
Expand Down Expand Up @@ -887,7 +887,12 @@ fn meta_tx_create_implicit_account(new_account: AccountId) {
node.view_account(&new_account).expect_err("account already exists");

let fee_helper = fee_helper(&node);
let initial_amount = nearcore::NEAR_BASE;
let initial_amount = match new_account.get_account_type() {
AccountType::NearImplicitAccount => NEAR_BASE,
// ETH-implicit accounts fit within zero-balance account limit.
AccountType::EthImplicitAccount => 0u128,
AccountType::NamedAccount => panic!("must be implicit"),
};
let actions = vec![Action::Transfer(TransferAction { deposit: initial_amount })];

let tx_cost = match new_account.get_account_type() {
Expand Down
Loading

0 comments on commit 68c190d

Please sign in to comment.