diff --git a/contracts/feature-tests/scenario-tester/tests/st_blackbox_test.rs b/contracts/feature-tests/scenario-tester/tests/st_blackbox_test.rs index cdcf610b25..d3f78692e8 100644 --- a/contracts/feature-tests/scenario-tester/tests/st_blackbox_test.rs +++ b/contracts/feature-tests/scenario-tester/tests/st_blackbox_test.rs @@ -244,3 +244,45 @@ fn set_state_test() { .balance(600) .esdt_balance(TOKEN_ID, 60); } + +#[test] +fn st_blackbox_tx_hash() { + let mut world = world(); + + world + .account(OWNER_ADDRESS) + .nonce(1) + .balance(100) + .account(OTHER_ADDRESS) + .nonce(2) + .balance(300) + .esdt_balance(TOKEN_ID, 500) + .commit(); + + let (new_address, tx_hash) = world + .tx() + .from(OWNER_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .init(5u32) + .code(CODE_PATH) + .new_address(ST_ADDRESS) + .tx_hash([11u8; 32]) + .returns(ReturnsNewAddress) + .returns(ReturnsTxHash) + .run(); + + assert_eq!(new_address, ST_ADDRESS.to_address()); + assert_eq!(tx_hash.as_array(), &[11u8; 32]); + + let tx_hash = world + .tx() + .from(OWNER_ADDRESS) + .to(ST_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .add(1u32) + .tx_hash([22u8; 32]) + .returns(ReturnsTxHash) + .run(); + + assert_eq!(tx_hash.as_array(), &[22u8; 32]); +} diff --git a/contracts/feature-tests/scenario-tester/tests/st_whitebox_test.rs b/contracts/feature-tests/scenario-tester/tests/st_whitebox_test.rs index 61dd6cdc2d..99e56d29f1 100644 --- a/contracts/feature-tests/scenario-tester/tests/st_whitebox_test.rs +++ b/contracts/feature-tests/scenario-tester/tests/st_whitebox_test.rs @@ -52,3 +52,36 @@ fn st_whitebox() { .check_account(SCENARIO_TESTER) .check_storage("str:sum", "8"); } + +#[test] +fn st_whitebox_tx_hash() { + let mut world = world(); + + world.account(OWNER).nonce(1); + + let (new_address, tx_hash) = world + .tx() + .from(OWNER) + .raw_deploy() + .code(ST_PATH_EXPR) + .new_address(SCENARIO_TESTER) + .tx_hash([11u8; 32]) + .returns(ReturnsNewBech32Address) + .returns(ReturnsTxHash) + .whitebox(scenario_tester::contract_obj, |sc| { + sc.init(BigUint::from(5u64)); + }); + + assert_eq!(new_address.to_address(), SCENARIO_TESTER.to_address()); + assert_eq!(tx_hash.as_array(), &[11u8; 32]); + + let tx_hash = world + .tx() + .from(OWNER) + .to(SCENARIO_TESTER) + .tx_hash([22u8; 32]) + .returns(ReturnsTxHash) + .whitebox(scenario_tester::contract_obj, |sc| sc.add(3u32.into())); + + assert_eq!(tx_hash.as_array(), &[22u8; 32]); +} diff --git a/framework/base/src/types/interaction/tx.rs b/framework/base/src/types/interaction/tx.rs index 905b9af8fc..b216ce0f28 100644 --- a/framework/base/src/types/interaction/tx.rs +++ b/framework/base/src/types/interaction/tx.rs @@ -908,11 +908,11 @@ where impl Tx where Env: TxEnvWithTxHash, - From: TxFromSpecified, + From: TxFrom, To: TxTo, - Payment: TxPaymentEgldOnly, + Payment: TxPayment, Gas: TxGas, - Data: TxDataFunctionCall, + Data: TxData, RH: TxResultHandler, { /// Sets the mock transaction hash to be used in a test. diff --git a/framework/scenario/src/facade/result_handlers/returns_tx_hash.rs b/framework/scenario/src/facade/result_handlers/returns_tx_hash.rs index 7494b41edc..fb30ea1ff3 100644 --- a/framework/scenario/src/facade/result_handlers/returns_tx_hash.rs +++ b/framework/scenario/src/facade/result_handlers/returns_tx_hash.rs @@ -1,3 +1,4 @@ +use multiversx_chain_vm::types::H256; use multiversx_sc::types::RHListItemExec; use crate::{ @@ -11,7 +12,7 @@ impl RHListItem for ReturnsTxHash where Env: TxEnv, { - type Returns = String; + type Returns = H256; } impl RHListItemExec for ReturnsTxHash @@ -19,6 +20,6 @@ where Env: TxEnv, { fn item_process_result(self, raw_result: &TxResponse) -> Self::Returns { - raw_result.tx_hash.clone() + raw_result.tx_hash.clone().expect("missing tx hash") } } diff --git a/framework/scenario/src/facade/world_tx/scenario_tx_whitebox.rs b/framework/scenario/src/facade/world_tx/scenario_tx_whitebox.rs index e20538ce0e..f8d03dc6da 100644 --- a/framework/scenario/src/facade/world_tx/scenario_tx_whitebox.rs +++ b/framework/scenario/src/facade/world_tx/scenario_tx_whitebox.rs @@ -1,5 +1,3 @@ -use core::str; - use crate::debug_executor::contract_instance_wrapped_execution; use crate::scenario::tx_to_step::TxToQueryStep; use crate::{ @@ -57,6 +55,7 @@ where let contract_obj = contract_obj_builder(); let mut step_wrapper = self.tx_to_step(); + step_wrapper.step.explicit_tx_hash = core::mem::take(&mut step_wrapper.env.data.tx_hash); let (new_address, tx_result) = step_wrapper .env .world @@ -70,15 +69,6 @@ where }); let mut response = TxResponse::from_tx_result(tx_result); - if let Some(tx_hash) = &step_wrapper.env.data.tx_hash { - let tx_hash_bytes = tx_hash.as_bytes(); - response.tx_hash = str::from_utf8(tx_hash_bytes) - .expect("tx hash not utf8 valid") - .to_string(); - } else { - response.tx_hash = String::from(""); - } - response.new_deployed_address = Some(new_address); step_wrapper.step.save_response(response); step_wrapper.process_result() @@ -133,6 +123,7 @@ where let contract_obj = contract_obj_builder(); let mut step_wrapper = self.tx_to_step(); + step_wrapper.step.explicit_tx_hash = core::mem::take(&mut step_wrapper.env.data.tx_hash); // no endpoint is called per se, but if it is empty, the VM thinks it is a simple transfer of value if step_wrapper.step.tx.function.is_empty() { @@ -151,16 +142,7 @@ where }); }); - let mut response = TxResponse::from_tx_result(tx_result); - if let Some(tx_hash) = &step_wrapper.env.data.tx_hash { - let tx_hash_bytes = tx_hash.as_bytes(); - response.tx_hash = str::from_utf8(tx_hash_bytes) - .expect("tx hash not utf8 valid") - .to_string(); - } else { - response.tx_hash = String::from(""); - } - + let response = TxResponse::from_tx_result(tx_result); step_wrapper.step.save_response(response); step_wrapper.process_result() } diff --git a/framework/scenario/src/scenario/model/step/sc_call_step.rs b/framework/scenario/src/scenario/model/step/sc_call_step.rs index 4a68bfa000..2a25fb2c2f 100644 --- a/framework/scenario/src/scenario/model/step/sc_call_step.rs +++ b/framework/scenario/src/scenario/model/step/sc_call_step.rs @@ -205,12 +205,13 @@ impl ScCallStep { .expect("SC call response not yet available") } - pub fn save_response(&mut self, tx_response: TxResponse) { + pub fn save_response(&mut self, mut tx_response: TxResponse) { if let Some(expect) = &mut self.expect { if expect.build_from_response { expect.update_from_response(&tx_response) } } + tx_response.tx_hash = self.explicit_tx_hash.as_ref().map(|vm_hash| vm_hash.as_array().into()); self.response = Some(tx_response); } } diff --git a/framework/scenario/src/scenario/model/step/sc_deploy_step.rs b/framework/scenario/src/scenario/model/step/sc_deploy_step.rs index 7e520e1a17..3b22522f69 100644 --- a/framework/scenario/src/scenario/model/step/sc_deploy_step.rs +++ b/framework/scenario/src/scenario/model/step/sc_deploy_step.rs @@ -135,13 +135,14 @@ impl ScDeployStep { .expect("SC deploy response not yet available") } - pub fn save_response(&mut self, response: TxResponse) { + pub fn save_response(&mut self, mut tx_response: TxResponse) { if let Some(expect) = &mut self.expect { if expect.build_from_response { - expect.update_from_response(&response) + expect.update_from_response(&tx_response) } } - self.response = Some(response); + tx_response.tx_hash = self.explicit_tx_hash.as_ref().map(|vm_hash| vm_hash.as_array().into()); + self.response = Some(tx_response); } } diff --git a/framework/scenario/src/scenario/model/transaction/tx_response.rs b/framework/scenario/src/scenario/model/transaction/tx_response.rs index b230fd1878..547b381338 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_response.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_response.rs @@ -1,4 +1,4 @@ -use multiversx_chain_vm::tx_mock::TxResult; +use multiversx_chain_vm::{tx_mock::TxResult, types::H256}; use multiversx_sc::types::Address; use super::{Log, TxExpect, TxResponseStatus}; @@ -20,8 +20,8 @@ pub struct TxResponse { pub gas: u64, /// The refund of the transaction. pub refund: u64, - /// The transaction hash - pub tx_hash: String, + /// The transaction hash, if available. + pub tx_hash: Option, } impl TxResponse { diff --git a/framework/snippets/src/network_response.rs b/framework/snippets/src/network_response.rs index 4684fdce3c..b5839e2e10 100644 --- a/framework/snippets/src/network_response.rs +++ b/framework/snippets/src/network_response.rs @@ -1,6 +1,6 @@ use multiversx_sc_scenario::{ imports::{Address, ESDTSystemSCAddress}, - multiversx_chain_vm::crypto_functions::keccak256, + multiversx_chain_vm::{crypto_functions::keccak256, types::H256}, scenario_model::{Log, TxResponse, TxResponseStatus}, }; use multiversx_sdk::{ @@ -17,7 +17,7 @@ pub fn parse_tx_response(tx: TransactionOnNetwork) -> TxResponse { if !tx_error.is_success() { return TxResponse { tx_error, - tx_hash: process_tx_hash(&tx).to_string(), + tx_hash: process_tx_hash(&tx), ..Default::default() }; } @@ -46,13 +46,17 @@ fn process_success(tx: &TransactionOnNetwork) -> TxResponse { new_deployed_address: process_new_deployed_address(tx), new_issued_token_identifier: process_new_issued_token_identifier(tx), logs: process_logs(tx), - tx_hash: process_tx_hash(tx).to_string(), + tx_hash: process_tx_hash(tx), ..Default::default() } } -fn process_tx_hash(tx: &TransactionOnNetwork) -> &str { - tx.hash.as_deref().unwrap_or("") +fn process_tx_hash(tx: &TransactionOnNetwork) -> Option { + tx.hash.as_ref().map(|encoded_hash| { + let decoded = hex::decode(encoded_hash).expect("error decoding tx hash from hex"); + assert_eq!(decoded.len(), 32); + H256::from_slice(&decoded) + }) } fn process_out(tx: &TransactionOnNetwork) -> Vec> {