Skip to content

Commit

Permalink
Test Wallet Contract
Browse files Browse the repository at this point in the history
  • Loading branch information
staffik committed Dec 7, 2023
1 parent 0bf0e9d commit 3051ba2
Show file tree
Hide file tree
Showing 17 changed files with 747 additions and 176 deletions.
724 changes: 595 additions & 129 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ members = [
"runtime/near-vm/compiler-test-derive",
"runtime/near-vm-runner",
"runtime/near-vm-runner/fuzz",
"runtime/near-wallet-contract",
"runtime/near-wallet-contract/wallet-contract",
"runtime/runtime",
"runtime/runtime-params-estimator",
"runtime/runtime-params-estimator/estimator-warehouse",
Expand Down Expand Up @@ -227,6 +229,7 @@ near-primitives-core = { path = "core/primitives-core" }
near-rosetta-rpc = { path = "chain/rosetta-rpc" }
near-rpc-error-core = { path = "tools/rpctypegen/core" }
near-rpc-error-macro = { path = "tools/rpctypegen/macro" }
near-sdk = "4.1.1"
near-stable-hasher = { path = "utils/near-stable-hasher" }
near-state-parts = { path = "tools/state-parts" }
near-state-parts-dump-check = { path = "tools/state-parts-dump-check" }
Expand Down Expand Up @@ -288,6 +291,7 @@ reqwest = { version = "0.11.14", features = ["blocking"] }
ripemd = "0.1.1"
rkyv = "0.7.31"
rlimit = "0.7"
rlp = "0.4.6"
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: 1 addition & 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
79 changes: 76 additions & 3 deletions integration-tests/src/tests/client/features/delegate_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ use crate::tests::standard_cases::fee_helper;
use near_chain::ChainGenesis;
use near_chain_configs::Genesis;
use near_client::test_utils::TestEnv;
use near_crypto::{KeyType, PublicKey, Signer};
use near_crypto::{KeyType, PublicKey, SecretKey, 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,
ActionError, ActionErrorKind, ActionsValidationError, FunctionCallError, InvalidAccessKeyError,
InvalidTxError, TxExecutionError,
};
use near_primitives::test_utils::{
create_user_test_signer, eth_implicit_test_account, near_implicit_test_account,
Expand All @@ -26,6 +26,7 @@ use near_primitives::transaction::{
DeployContractAction, FunctionCallAction, StakeAction, TransferAction,
};
use near_primitives::types::{AccountId, Balance};
use near_primitives::utils::derive_eth_implicit_account_id;
use near_primitives::version::{ProtocolFeature, ProtocolVersion, PROTOCOL_VERSION};
use near_primitives::views::{
AccessKeyPermissionView, ExecutionStatusView, FinalExecutionOutcomeView, FinalExecutionStatus,
Expand All @@ -34,6 +35,7 @@ use near_test_contracts::{ft_contract, smallest_rs_contract};
use nearcore::config::GenesisExt;
use nearcore::test_utils::TestEnvNightshadeSetupExt;
use nearcore::NEAR_BASE;
use rlp::RlpStream;
use testlib::runtime_utils::{
add_account_with_access_key, add_contract, add_test_contract, alice_account, bob_account,
carol_account, eve_dot_alice_account,
Expand Down Expand Up @@ -945,3 +947,74 @@ fn meta_tx_create_eth_implicit_account() {
}
meta_tx_create_implicit_account(eth_implicit_test_account());
}

fn meta_tx_call_wallet_contract(authorized: bool) {
let genesis = Genesis::test(vec![alice_account(), bob_account(), carol_account()], 3);
let relayer = alice_account();
let node = RuntimeNode::new_from_genesis(&relayer, genesis);
let sender = bob_account();

let secret_key = SecretKey::from_seed(KeyType::SECP256K1, "test");
let public_key = secret_key.public_key();
let eth_implicit_account = derive_eth_implicit_account_id(public_key.unwrap_as_secp256k1());
let other_public_key = SecretKey::from_seed(KeyType::SECP256K1, "test2").public_key();

let initial_amount = 50 * NEAR_BASE;
let actions = vec![Action::Transfer(TransferAction { deposit: initial_amount })];
node.user()
.meta_tx(sender.clone(), eth_implicit_account.clone(), relayer.clone(), actions)
.unwrap()
.assert_success();

let target = carol_account();
let transfer_amount = NEAR_BASE;
let initial_balance = node.view_balance(&target).expect("failed looking up balance");

let mut stream = RlpStream::new_list(3);
stream.append(&target.as_str());
// The RLP trait `Encodable` is not implemented for `u128`.
stream.append(&transfer_amount.to_be_bytes().as_slice());
if authorized {
stream.append(&public_key.key_data());
} else {
stream.append(&other_public_key.key_data());
}
let rlp_encoded_data = stream.out().to_vec();

let args = serde_json::json!({
"target": target.to_string(),
"rlp_transaction": rlp_encoded_data,
})
.to_string()
.into_bytes();

let actions = vec![Action::FunctionCall(Box::new(FunctionCallAction {
method_name: "execute_rlp".to_owned(),
args,
gas: 30_000_000_000_000,
deposit: 0,
}))];
let tx_result = node.user().meta_tx(sender, eth_implicit_account, relayer, actions).unwrap();
let wallet_contract_call_result = &tx_result.receipts_outcome[1].outcome.status;

if authorized {
tx_result.assert_success();
let final_balance = node.view_balance(&target).expect("failed looking up balance");
assert_eq!(final_balance, initial_balance + transfer_amount);
} else {
let expected_error = near_primitives::views::ExecutionStatusView::Failure(TxExecutionError::ActionError(
ActionError { index: Some(0), kind: ActionErrorKind::FunctionCallError { 0: FunctionCallError::ExecutionError("Smart contract panicked: Public key does not match the Wallet Contract address.".to_string()) }}
));
assert_eq!(wallet_contract_call_result, &expected_error);
}
}

#[test]
fn meta_tx_call_wallet_contract_authorized() {
meta_tx_call_wallet_contract(true);
}

#[test]
fn meta_tx_call_wallet_contract_unauthorized() {
meta_tx_call_wallet_contract(false);
}
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 @@ -330,7 +330,7 @@ pub fn test_send_money(node: impl Node) {
);
}

pub fn transfer_tokens_implicit_account(node: impl Node, public_key: PublicKey) {
pub fn transfer_tokens_to_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();
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/src/tests/standard_cases/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ fn test_send_money_runtime() {
fn test_transfer_tokens_near_implicit_account_runtime() {
let node = create_runtime_node();
let public_key = node.user().signer().public_key();
transfer_tokens_implicit_account(node, public_key);
transfer_tokens_to_implicit_account(node, public_key);
}

#[test]
Expand All @@ -130,7 +130,7 @@ fn test_transfer_tokens_eth_implicit_account_runtime() {
}
let node = create_runtime_node();
let secret_key = SecretKey::from_seed(KeyType::SECP256K1, "test");
transfer_tokens_implicit_account(node, secret_key.public_key());
transfer_tokens_to_implicit_account(node, secret_key.public_key());
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
<<<<<<< HEAD
=======
#[cfg(not(test))]
extern crate proc_macro;
>>>>>>> 27e96cad2 (compiler-test-derive: Switch to proc_macro exclusively)
use proc_macro::TokenStream;
use quote::quote;
use std::path::PathBuf;
Expand Down
2 changes: 1 addition & 1 deletion runtime/near-wallet-contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "A temporary Wallet Contract placeholder."
description = "Builds and exposes a temporary Wallet Contract wasm file."
repository.workspace = true
license.workspace = true
publish = false
Expand Down
1 change: 0 additions & 1 deletion runtime/near-wallet-contract/LICENSE-APACHE

This file was deleted.

1 change: 0 additions & 1 deletion runtime/near-wallet-contract/LICENSE-MIT

This file was deleted.

8 changes: 2 additions & 6 deletions runtime/near-wallet-contract/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ fn main() {
}

fn try_main() -> Result<(), Error> {
build_contract("./wallet-contract", &["--features", "latest_protocol"], "wallet_contract")?;
build_contract(
"./wallet-contract",
&["--features", "latest_protocol,nightly"],
"nightly_wallet_contract",
)?;
build_contract("./wallet-contract", &[], "wallet_contract")?;
build_contract("./wallet-contract", &["--features", "nightly"], "nightly_wallet_contract")?;
Ok(())
}

Expand Down
Binary file modified runtime/near-wallet-contract/res/nightly_wallet_contract.wasm
Binary file not shown.
Binary file modified runtime/near-wallet-contract/res/wallet_contract.wasm
Binary file not shown.
11 changes: 11 additions & 0 deletions runtime/near-wallet-contract/wallet-contract/Cargo.lock

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

37 changes: 15 additions & 22 deletions runtime/near-wallet-contract/wallet-contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
[package]
name = "wallet-contract"
version = "0.1.0"
authors = ["Near Inc <[email protected]>"]
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "A placeholder implementation of the Wallet Contract."
repository.workspace = true
license.workspace = true
publish = false
edition = "2021"

[lints]
workspace = true

[lib]
crate-type = ["cdylib"]

[dependencies]
base64 = "0.21"
serde_json = "1"
near-sdk = "4.1.1"

[profile.release]
codegen-units = 1
# Tell `rustc` to optimize for small code size.
opt-level = "z"
strip = true
lto = true
debug = false
panic = "abort"
rpath = false
debug-assertions = false
incremental = false

[workspace]
members = []
base64.workspace = true
hex.workspace = true
serde_json.workspace = true
near-sdk.workspace = true
rlp.workspace = true

[features]
nightly = []
latest_protocol = []
44 changes: 38 additions & 6 deletions runtime/near-wallet-contract/wallet-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,50 @@
//! Temporary implementation of the Wallet Contract.
//! See https://github.com/near/NEPs/issues/518.
//! Must not use in production!

// TODO(eth-implicit) Change to a real Wallet Contract implementation.

use hex;
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{near_bindgen, AccountId, Promise, Balance};
use near_sdk::{env, near_bindgen, AccountId, Promise};
use rlp::Rlp;

#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct WalletContract { }
pub struct WalletContract {}

#[near_bindgen]
impl WalletContract {
pub fn transfer(&self, to: AccountId, amount: Balance){
Promise::new(to).transfer(amount);
}
pub fn execute_rlp(&self, target: AccountId, rlp_transaction: Vec<u8>) {
let rlp = Rlp::new(&rlp_transaction);

let to: String = match rlp.val_at(0) {
Ok(to) => to,
_ => env::panic_str("Missing `to` field in RLP-encoded transaction."),
};
if target.to_string() != to {
env::panic_str("`target` equals the transaction's `To` address.");
}

let value_bytes: Vec<u8> = match rlp.val_at(1) {
Ok(value_bytes) => value_bytes,
_ => env::panic_str("Missing `value` field in RLP-encoded transaction."),
};
let value = u128::from_be_bytes(
value_bytes.try_into().expect("Incorrect `value` field in RLP-encoded transaction."),
);

let signer_public_key_bytes: Vec<u8> = match rlp.val_at(2) {
Ok(signer_public_key_bytes) => signer_public_key_bytes,
_ => env::panic_str("Signature extraction failed for RLP-encoded transaction."),
};

let hash = env::keccak256(&signer_public_key_bytes);
let signer_address = format!("0x{}", hex::encode(&hash[12..32]));

if signer_address != env::current_account_id().to_string() {
env::panic_str("Public key does not match the Wallet Contract address.");
}

Promise::new(target).transfer(value);
}
}
2 changes: 1 addition & 1 deletion runtime/runtime/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ fn receipt_required_gas(apply_state: &ApplyState, receipt: &Receipt) -> Result<G

/// Validate access key which was used for signing DelegateAction:
///
/// - Checks whether the access key is present fo given public_key and sender_id.
/// - Checks whether the access key is present for given public_key and sender_id.
/// - Validates nonce and updates it if it's ok.
/// - Validates access key permissions.
fn validate_delegate_action_key(
Expand Down

0 comments on commit 3051ba2

Please sign in to comment.