diff --git a/etc/state-migration-test/Cargo.lock b/etc/state-migration-test/Cargo.lock index bfb0aedf8..cae56b329 100644 --- a/etc/state-migration-test/Cargo.lock +++ b/etc/state-migration-test/Cargo.lock @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "aurora-engine" -version = "1.6.0" +version = "1.6.1" dependencies = [ "aurora-bn", "base64", diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 0b687f0af..ae4b3f2fa 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -11,9 +11,10 @@ use near_vm_runner::{MockCompiledContractCache, VMError}; use primitive_types::U256; use rlp::RlpStream; use secp256k1::{self, Message, PublicKey, SecretKey}; +use std::borrow::Cow; use crate::fungible_token::{FungibleToken, FungibleTokenMetadata}; -use crate::parameters::{InitCallArgs, NewCallArgs, SubmitResult, TransactionStatus}; +use crate::parameters::{InitCallArgs, NewCallArgs, SubmitResult, TransactionStatus, ViewCallArgs}; use crate::prelude::Address; use crate::storage; use crate::test_utils::solidity::{ContractConstructor, DeployedContract}; @@ -74,7 +75,6 @@ pub(crate) struct AuroraRunner { pub wasm_config: VMConfig, pub fees_config: RuntimeFeesConfig, pub current_protocol_version: u32, - pub profile: ProfileData, pub previous_logs: Vec, } @@ -89,10 +89,32 @@ pub(crate) struct OneShotAuroraRunner<'a> { impl<'a> OneShotAuroraRunner<'a> { pub fn call( + self, + method_name: &str, + caller_account_id: String, + input: Vec, + ) -> (Option, Option) { + self.call_with_optional_profile(method_name, caller_account_id, input, None) + } + + pub fn profiled_call( + self, + method_name: &str, + caller_account_id: String, + input: Vec, + ) -> (Option, Option, ProfileData) { + let profile = Default::default(); + let (outcome, error) = + self.call_with_optional_profile(method_name, caller_account_id, input, Some(&profile)); + (outcome, error, profile) + } + + fn call_with_optional_profile( mut self, method_name: &str, caller_account_id: String, input: Vec, + maybe_profile: Option<&ProfileData>, ) -> (Option, Option) { AuroraRunner::update_context( &mut self.context, @@ -101,6 +123,7 @@ impl<'a> OneShotAuroraRunner<'a> { input, ); + let profile = maybe_profile.map(Cow::Borrowed).unwrap_or_default(); near_vm_runner::run( &self.base.code, method_name, @@ -111,7 +134,7 @@ impl<'a> OneShotAuroraRunner<'a> { &[], self.base.current_protocol_version, Some(&self.base.cache), - &self.base.profile, + profile.as_ref(), ) } } @@ -149,15 +172,36 @@ impl AuroraRunner { caller_account_id.clone(), caller_account_id, input, + None, ) } + // Might be useful for optimizing performance in the future + #[allow(dead_code)] + pub fn profiled_call( + &mut self, + method_name: &str, + caller_account_id: String, + input: Vec, + ) -> (Option, Option, ProfileData) { + let profile = Default::default(); + let (outcome, error) = self.call_with_signer( + method_name, + caller_account_id.clone(), + caller_account_id, + input, + Some(&profile), + ); + (outcome, error, profile) + } + pub fn call_with_signer( &mut self, method_name: &str, caller_account_id: String, signer_account_id: String, input: Vec, + maybe_profile: Option<&ProfileData>, ) -> (Option, Option) { Self::update_context( &mut self.context, @@ -166,6 +210,7 @@ impl AuroraRunner { input, ); + let profile = maybe_profile.map(Cow::Borrowed).unwrap_or_default(); let (maybe_outcome, maybe_error) = near_vm_runner::run( &self.code, method_name, @@ -176,7 +221,7 @@ impl AuroraRunner { &[], self.current_protocol_version, Some(&self.cache), - &self.profile, + profile.as_ref(), ); if let Some(outcome) = &maybe_outcome { self.context.storage_usage = outcome.storage_usage; @@ -265,6 +310,29 @@ impl AuroraRunner { } } + pub fn view_call(&self, args: ViewCallArgs) -> Result { + let input = args.try_to_vec().unwrap(); + let (outcome, maybe_error) = self.one_shot().call("view", "VIEWER".to_string(), input); + Ok( + TransactionStatus::try_from_slice(&Self::bytes_from_outcome(outcome, maybe_error)?) + .unwrap(), + ) + } + + pub fn profiled_view_call( + &self, + args: ViewCallArgs, + ) -> (Result, ProfileData) { + let input = args.try_to_vec().unwrap(); + let (outcome, maybe_error, profile) = + self.one_shot() + .profiled_call("view", "VIEWER".to_string(), input); + let status = Self::bytes_from_outcome(outcome, maybe_error) + .map(|bytes| TransactionStatus::try_from_slice(&bytes).unwrap()); + + (status, profile) + } + pub fn get_balance(&self, address: Address) -> types::Wei { types::Wei::new(self.getter_method_call("get_balance", address)) } @@ -276,29 +344,27 @@ impl AuroraRunner { // Used in `get_balance` and `get_nonce`. This function exists to avoid code duplication // since the contract's `get_nonce` and `get_balance` have the same type signature. fn getter_method_call(&self, method_name: &str, address: Address) -> U256 { - let mut context = self.context.clone(); - Self::update_context( - &mut context, - "GETTER".to_string(), + let (outcome, maybe_error) = self.one_shot().call( + method_name, "GETTER".to_string(), address.as_bytes().to_vec(), ); - let (outcome, maybe_error) = near_vm_runner::run( - &self.code, - method_name, - &mut self.ext.clone(), - context, - &self.wasm_config, - &self.fees_config, - &[], - self.current_protocol_version, - Some(&self.cache), - &self.profile, - ); assert!(maybe_error.is_none()); let bytes = outcome.unwrap().return_data.as_value().unwrap(); U256::from_big_endian(&bytes) } + + fn bytes_from_outcome( + maybe_outcome: Option, + maybe_error: Option, + ) -> Result, VMError> { + if let Some(error) = maybe_error { + Err(error) + } else { + let bytes = maybe_outcome.unwrap().return_data.as_value().unwrap(); + Ok(bytes) + } + } } impl Default for AuroraRunner { @@ -339,7 +405,6 @@ impl Default for AuroraRunner { wasm_config: Default::default(), fees_config: Default::default(), current_protocol_version: u32::MAX, - profile: Default::default(), previous_logs: Default::default(), } } @@ -408,6 +473,15 @@ pub(crate) fn create_eth_transaction( sign_transaction(tx, chain_id, secret_key) } +pub(crate) fn as_view_call(tx: LegacyEthTransaction, sender: Address) -> ViewCallArgs { + ViewCallArgs { + sender: sender.0, + address: tx.to.unwrap().0, + amount: tx.value.to_bytes(), + input: tx.data, + } +} + pub(crate) fn sign_transaction( tx: LegacyEthTransaction, chain_id: Option, diff --git a/src/tests/erc20.rs b/src/tests/erc20.rs index 13f953a68..77046f251 100644 --- a/src/tests/erc20.rs +++ b/src/tests/erc20.rs @@ -20,7 +20,7 @@ fn erc20_mint() { // Validate pre-state assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &mut source_account, dest_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) ); // Do mint transaction @@ -33,7 +33,7 @@ fn erc20_mint() { // Validate post-state assert_eq!( U256::from(mint_amount), - get_address_erc20_balance(&mut runner, &mut source_account, dest_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) ); } @@ -44,7 +44,7 @@ fn erc20_mint_out_of_gas() { // Validate pre-state assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &mut source_account, dest_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) ); // Try mint transaction @@ -69,11 +69,12 @@ fn erc20_mint_out_of_gas() { assert_eq!(error.status, TransactionStatus::OutOfGas); // Validate post-state + test_utils::validate_address_balance_and_nonce( &runner, test_utils::address_from_secret_key(&source_account.secret_key), Wei::new_u64(INITIAL_BALANCE - GAS_LIMIT * GAS_PRICE), - (INITIAL_NONCE + 3).into(), + (INITIAL_NONCE + 2).into(), ); test_utils::validate_address_balance_and_nonce( &runner, @@ -83,6 +84,27 @@ fn erc20_mint_out_of_gas() { ); } +#[test] +fn profile_erc20_get_balance() { + let (mut runner, mut source_account, _, contract) = initialize_erc20(); + let source_address = test_utils::address_from_secret_key(&source_account.secret_key); + + let outcome = runner.submit_with_signer(&mut source_account, |nonce| { + contract.mint(source_address, INITIAL_BALANCE.into(), nonce) + }); + assert!(outcome.is_ok()); + + let balance_tx = contract.balance_of(source_address, U256::zero()); + let (result, profile) = + runner.profiled_view_call(test_utils::as_view_call(balance_tx, source_address)); + assert!(result.is_ok()); + + // call costs less than 6 Tgas + assert!(profile.all_gas() / 1_000_000_000_000 < 6); + // at least 70% of the cost is spent on wasm computation (as opposed to host functions) + assert!((100 * profile.wasm_gas()) / profile.all_gas() > 70); +} + #[test] fn erc20_transfer_success() { let (mut runner, mut source_account, dest_address, contract) = initialize_erc20(); @@ -96,11 +118,11 @@ fn erc20_transfer_success() { // Validate pre-state assert_eq!( U256::from(INITIAL_BALANCE), - get_address_erc20_balance(&mut runner, &mut source_account, source_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) ); assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &mut source_account, dest_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) ); // Do transfer @@ -114,11 +136,11 @@ fn erc20_transfer_success() { // Validate post-state assert_eq!( U256::from(INITIAL_BALANCE - TRANSFER_AMOUNT), - get_address_erc20_balance(&mut runner, &mut source_account, source_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) ); assert_eq!( U256::from(TRANSFER_AMOUNT), - get_address_erc20_balance(&mut runner, &mut source_account, dest_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) ); } @@ -135,11 +157,11 @@ fn erc20_transfer_insufficient_balance() { // Validate pre-state assert_eq!( U256::from(INITIAL_BALANCE), - get_address_erc20_balance(&mut runner, &mut source_account, source_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) ); assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &mut source_account, dest_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) ); // Do transfer @@ -154,11 +176,11 @@ fn erc20_transfer_insufficient_balance() { // Validate post-state assert_eq!( U256::from(INITIAL_BALANCE), - get_address_erc20_balance(&mut runner, &mut source_account, source_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, source_address, &contract) ); assert_eq!( U256::zero(), - get_address_erc20_balance(&mut runner, &mut source_account, dest_address, &contract) + get_address_erc20_balance(&mut runner, &source_account, dest_address, &contract) ); } @@ -205,15 +227,22 @@ fn deploy_erc_20_out_of_gas() { fn get_address_erc20_balance( runner: &mut test_utils::AuroraRunner, - signer: &mut Signer, + signer: &Signer, address: Address, contract: &ERC20, ) -> U256 { - let outcome = runner - .submit_with_signer(signer, |nonce| contract.balance_of(address, nonce)) + let balance_tx = contract.balance_of(address, signer.nonce.into()); + let result = runner + .view_call(test_utils::as_view_call( + balance_tx, + test_utils::address_from_secret_key(&signer.secret_key), + )) .unwrap(); - let output = test_utils::unwrap_success(outcome); - U256::from_big_endian(&output) + let bytes = match result { + crate::parameters::TransactionStatus::Succeed(bytes) => bytes, + err => panic!("Unexpected view call status {:?}", err), + }; + U256::from_big_endian(&bytes) } fn parse_erc20_error_message(result: &[u8]) -> String { diff --git a/src/tests/erc20_connector.rs b/src/tests/erc20_connector.rs index 79d839388..c930f2848 100644 --- a/src/tests/erc20_connector.rs +++ b/src/tests/erc20_connector.rs @@ -87,8 +87,13 @@ impl test_utils::AuroraRunner { signer_account_id: String, input: Vec, ) -> CallResult { - let (outcome, error) = - self.call_with_signer(method_name, caller_account_id, signer_account_id, input); + let (outcome, error) = self.call_with_signer( + method_name, + caller_account_id, + signer_account_id, + input, + None, + ); CallResult { outcome, error } }