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/base/src/types/interaction/tx_env.rs b/framework/base/src/types/interaction/tx_env.rs index 37e18d7dec..188e304311 100644 --- a/framework/base/src/types/interaction/tx_env.rs +++ b/framework/base/src/types/interaction/tx_env.rs @@ -27,4 +27,7 @@ pub trait TxEnvMockDeployAddress: TxEnv { pub trait TxEnvWithTxHash: TxEnv { fn set_tx_hash(&mut self, tx_hash: H256); + + /// Retrieves current tx hash, while resetting it in self. + fn take_tx_hash(&mut self) -> Option; } diff --git a/framework/scenario/src/facade/result_handlers.rs b/framework/scenario/src/facade/result_handlers.rs index 33b5b1a495..88e90d0cd5 100644 --- a/framework/scenario/src/facade/result_handlers.rs +++ b/framework/scenario/src/facade/result_handlers.rs @@ -7,6 +7,7 @@ mod returns_message; mod returns_new_bech32_address; mod returns_new_token_identifier; mod returns_status; +mod returns_tx_hash; mod with_tx_raw_response; pub use expect_error::ExpectError; @@ -18,4 +19,5 @@ pub use returns_message::ReturnsMessage; pub use returns_new_bech32_address::ReturnsNewBech32Address; pub use returns_new_token_identifier::ReturnsNewTokenIdentifier; pub use returns_status::ReturnsStatus; +pub use returns_tx_hash::ReturnsTxHash; pub use with_tx_raw_response::WithRawTxResponse; diff --git a/framework/scenario/src/facade/result_handlers/returns_tx_hash.rs b/framework/scenario/src/facade/result_handlers/returns_tx_hash.rs new file mode 100644 index 0000000000..fb30ea1ff3 --- /dev/null +++ b/framework/scenario/src/facade/result_handlers/returns_tx_hash.rs @@ -0,0 +1,25 @@ +use multiversx_chain_vm::types::H256; +use multiversx_sc::types::RHListItemExec; + +use crate::{ + multiversx_sc::types::{RHListItem, TxEnv}, + scenario_model::TxResponse, +}; + +pub struct ReturnsTxHash; + +impl RHListItem for ReturnsTxHash +where + Env: TxEnv, +{ + type Returns = H256; +} + +impl RHListItemExec for ReturnsTxHash +where + Env: TxEnv, +{ + fn item_process_result(self, raw_result: &TxResponse) -> Self::Returns { + raw_result.tx_hash.clone().expect("missing tx hash") + } +} diff --git a/framework/scenario/src/facade/world_tx/scenario_exec_call.rs b/framework/scenario/src/facade/world_tx/scenario_exec_call.rs index 8905c7d14d..ca09b9d318 100644 --- a/framework/scenario/src/facade/world_tx/scenario_exec_call.rs +++ b/framework/scenario/src/facade/world_tx/scenario_exec_call.rs @@ -85,7 +85,6 @@ where fn run(self) -> Self::Returns { let mut step_wrapper = self.tx_to_step(); - step_wrapper.step.explicit_tx_hash = core::mem::take(&mut step_wrapper.env.data.tx_hash); step_wrapper.env.world.sc_call(&mut step_wrapper.step); step_wrapper.process_result() } @@ -111,7 +110,6 @@ where fn run(self) -> Self::Returns { let mut step_wrapper = self.tx_to_step(); - step_wrapper.step.explicit_tx_hash = core::mem::take(&mut step_wrapper.env.data.tx_hash); step_wrapper.env.world.sc_call(&mut step_wrapper.step); step_wrapper.process_result() } @@ -119,8 +117,11 @@ where impl<'w> TxEnvWithTxHash for ScenarioEnvExec<'w> { fn set_tx_hash(&mut self, tx_hash: H256) { - assert!(self.data.tx_hash.is_none(), "tx hash set twice"); - self.data.tx_hash = Some(tx_hash); + self.data.set_tx_hash(tx_hash); + } + + fn take_tx_hash(&mut self) -> Option { + self.data.take_tx_hash() } } diff --git a/framework/scenario/src/facade/world_tx/scenario_exec_deploy.rs b/framework/scenario/src/facade/world_tx/scenario_exec_deploy.rs index 40fbbaa878..866d4f562f 100644 --- a/framework/scenario/src/facade/world_tx/scenario_exec_deploy.rs +++ b/framework/scenario/src/facade/world_tx/scenario_exec_deploy.rs @@ -35,7 +35,6 @@ where fn run(self) -> Self::Returns { let mut step_wrapper = self.tx_to_step(); - step_wrapper.step.explicit_tx_hash = core::mem::take(&mut step_wrapper.env.data.tx_hash); step_wrapper.env.world.sc_deploy(&mut step_wrapper.step); step_wrapper.process_result() } diff --git a/framework/scenario/src/facade/world_tx/scenario_tx_env.rs b/framework/scenario/src/facade/world_tx/scenario_tx_env.rs index 339ed9b38b..c3a2a1c204 100644 --- a/framework/scenario/src/facade/world_tx/scenario_tx_env.rs +++ b/framework/scenario/src/facade/world_tx/scenario_tx_env.rs @@ -1,5 +1,5 @@ use multiversx_chain_scenario_format::interpret_trait::InterpreterContext; -use multiversx_sc::types::{ManagedAddress, ManagedBuffer, TxEnv, H256}; +use multiversx_sc::types::{ManagedAddress, ManagedBuffer, TxEnv, TxEnvWithTxHash, H256}; use crate::{api::StaticApi, scenario_model::TxExpect, ScenarioWorld}; @@ -33,6 +33,17 @@ impl TxEnv for ScenarioTxEnvData { } } +impl TxEnvWithTxHash for ScenarioTxEnvData { + fn set_tx_hash(&mut self, tx_hash: H256) { + assert!(self.tx_hash.is_none(), "tx hash set twice"); + self.tx_hash = Some(tx_hash); + } + + fn take_tx_hash(&mut self) -> Option { + core::mem::take(&mut self.tx_hash) + } +} + impl ScenarioTxEnvData { pub fn interpreter_context(&self) -> InterpreterContext { self.interpreter_context.clone() 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..8d8d71b0e8 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,16 @@ 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..6090cfd492 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,17 @@ 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 8a7a5c4bf9..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,6 +20,8 @@ pub struct TxResponse { pub gas: u64, /// The refund of the transaction. pub refund: u64, + /// The transaction hash, if available. + pub tx_hash: Option, } impl TxResponse { diff --git a/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs b/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs index 475326eac7..1d1eb2c9a2 100644 --- a/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs +++ b/framework/scenario/src/scenario/tx_to_step/tx_to_step_call.rs @@ -1,6 +1,6 @@ use multiversx_sc::types::{ - Code, FunctionCall, NotPayable, RHListExec, Tx, TxEnv, TxFromSpecified, TxGas, TxPayment, - TxToSpecified, UpgradeCall, + Code, FunctionCall, NotPayable, RHListExec, Tx, TxEnv, TxEnvWithTxHash, TxFromSpecified, TxGas, + TxPayment, TxToSpecified, UpgradeCall, }; use crate::{ @@ -14,7 +14,7 @@ use super::{address_annotated, gas_annotated, StepWrapper, TxToStep}; impl TxToStep for Tx, RH> where - Env: TxEnv, + Env: TxEnvWithTxHash, From: TxFromSpecified, To: TxToSpecified, Payment: TxPayment, @@ -23,7 +23,7 @@ where { type Step = ScCallStep; - fn tx_to_step(self) -> StepWrapper { + fn tx_to_step(mut self) -> StepWrapper { let mut step = tx_to_sc_call_step( &self.env, self.from, @@ -32,6 +32,7 @@ where self.gas, self.data, ); + step.explicit_tx_hash = self.env.take_tx_hash(); step.expect = Some(self.result_handler.list_tx_expect()); StepWrapper { diff --git a/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs b/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs index 4e49be51b4..1c7532e27a 100644 --- a/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs +++ b/framework/scenario/src/scenario/tx_to_step/tx_to_step_deploy.rs @@ -1,5 +1,6 @@ use multiversx_sc::types::{ - Code, DeployCall, RHListExec, Tx, TxCodeValue, TxEnv, TxFromSpecified, TxGas, TxPayment, + Code, DeployCall, RHListExec, Tx, TxCodeValue, TxEnv, TxEnvWithTxHash, TxFromSpecified, TxGas, + TxPayment, }; use crate::scenario_model::{ScDeployStep, TxExpect, TxResponse}; @@ -9,7 +10,7 @@ use super::{address_annotated, code_annotated, gas_annotated, StepWrapper, TxToS impl TxToStep for Tx>, RH> where - Env: TxEnv, + Env: TxEnvWithTxHash, From: TxFromSpecified, Payment: TxPayment, Gas: TxGas, @@ -18,9 +19,10 @@ where { type Step = ScDeployStep; - fn tx_to_step(self) -> StepWrapper { + fn tx_to_step(mut self) -> StepWrapper { let mut step = tx_to_sc_deploy_step(&self.env, self.from, self.payment, self.gas, self.data); + step.explicit_tx_hash = self.env.take_tx_hash(); step.expect = Some(self.result_handler.list_tx_expect()); StepWrapper { diff --git a/framework/snippets/src/interactor_tx/interactor_exec_env.rs b/framework/snippets/src/interactor_tx/interactor_exec_env.rs index 6473c50a91..436e65ac0c 100644 --- a/framework/snippets/src/interactor_tx/interactor_exec_env.rs +++ b/framework/snippets/src/interactor_tx/interactor_exec_env.rs @@ -1,6 +1,8 @@ use multiversx_sc_scenario::{ api::StaticApi, - multiversx_sc::types::{ManagedAddress, ManagedBuffer, Tx, TxBaseWithEnv, TxEnv}, + multiversx_sc::types::{ + ManagedAddress, ManagedBuffer, Tx, TxBaseWithEnv, TxEnv, TxEnvWithTxHash, H256, + }, scenario_model::TxExpect, ScenarioTxEnv, ScenarioTxEnvData, }; @@ -44,3 +46,13 @@ impl<'w> ScenarioTxEnv for InteractorEnvExec<'w> { &self.data } } + +impl<'w> TxEnvWithTxHash for InteractorEnvExec<'w> { + fn set_tx_hash(&mut self, tx_hash: H256) { + self.data.set_tx_hash(tx_hash); + } + + fn take_tx_hash(&mut self) -> Option { + self.data.take_tx_hash() + } +} diff --git a/framework/snippets/src/network_response.rs b/framework/snippets/src/network_response.rs index 0140dd839e..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,6 +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), ..Default::default() }; } @@ -45,10 +46,19 @@ 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), ..Default::default() } } +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> { let out_scr = tx.smart_contract_results.iter().find(is_out_scr);