diff --git a/contracts/feature-tests/rust-testing-framework-tester/scenarios/test.scen.json b/contracts/feature-tests/rust-testing-framework-tester/scenarios/test.scen.json index 17477882fa..6e78f8e444 100644 --- a/contracts/feature-tests/rust-testing-framework-tester/scenarios/test.scen.json +++ b/contracts/feature-tests/rust-testing-framework-tester/scenarios/test.scen.json @@ -35,6 +35,7 @@ "step": "checkState", "accounts": { "0x0000000000000000fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e": { + "nonce": "*", "balance": "0", "esdt": {}, "storage": { @@ -62,13 +63,15 @@ "expect": { "out": [], "status": "0", - "message": "str:" + "message": "str:", + "refund": "*" } }, { "step": "checkState", "accounts": { "0x0000000000000000fb1397e8225ea85e0f0e6e8c7b126d0016ccbde0e667151e": { + "nonce": "*", "balance": "0", "esdt": {}, "storage": { @@ -95,7 +98,8 @@ "0x33" ], "status": "0", - "message": "str:" + "message": "str:", + "refund": "*" } } ] diff --git a/contracts/feature-tests/rust-testing-framework-tester/scenarios/test_esdt_generation.scen.json b/contracts/feature-tests/rust-testing-framework-tester/scenarios/test_esdt_generation.scen.json index 293b6b9c74..395b448b81 100644 --- a/contracts/feature-tests/rust-testing-framework-tester/scenarios/test_esdt_generation.scen.json +++ b/contracts/feature-tests/rust-testing-framework-tester/scenarios/test_esdt_generation.scen.json @@ -36,6 +36,7 @@ "step": "checkState", "accounts": { "0x00000000000000006c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925": { + "nonce": "*", "balance": "0", "esdt": { "str:COOL-123456": { diff --git a/framework/scenario/src/facade/scenario_world.rs b/framework/scenario/src/facade/scenario_world.rs index 9ead1a9280..6a4f0ce82b 100644 --- a/framework/scenario/src/facade/scenario_world.rs +++ b/framework/scenario/src/facade/scenario_world.rs @@ -96,7 +96,7 @@ impl ScenarioWorld { } } - pub(crate) fn get_state(&self) -> &BlockchainState { + pub fn get_state(&self) -> &BlockchainState { &self.get_debugger_backend().vm_runner.blockchain_mock.state } @@ -113,6 +113,11 @@ impl ScenarioWorld { self } + pub fn start_trace_with(&mut self, trace: ScenarioTrace) -> &mut Self { + self.get_mut_debugger_backend().trace = Some(trace); + self + } + /// Tells the tests where the crate lies relative to the workspace. /// This ensures that the paths are set correctly, including in debug mode. pub fn set_current_dir_from_workspace(&mut self, relative_path: &str) -> &mut Self { diff --git a/framework/scenario/src/scenario/model/account_data/account.rs b/framework/scenario/src/scenario/model/account_data/account.rs index 912d8d94f5..3c049e5fcf 100644 --- a/framework/scenario/src/scenario/model/account_data/account.rs +++ b/framework/scenario/src/scenario/model/account_data/account.rs @@ -21,6 +21,7 @@ pub struct Account { pub code: Option, pub owner: Option, pub developer_rewards: Option, + pub update: Option, } impl Account { @@ -176,6 +177,11 @@ impl Account { self.owner = Some(AddressValue::from(owner_expr)); self } + + pub fn update(mut self, update: bool) -> Self { + self.update = Some(update); + self + } } impl InterpretableFrom for Account { @@ -214,6 +220,7 @@ impl InterpretableFrom for Account { developer_rewards: from .developer_rewards .map(|b| BigUintValue::interpret_from(b, context)), + update: from.update, } } } @@ -238,6 +245,7 @@ impl IntoRaw for Account { code: self.code.map(|n| n.original), owner: self.owner.map(|n| n.original), developer_rewards: self.developer_rewards.map(|n| n.original), + update: self.update, } } } diff --git a/framework/scenario/src/scenario/model/account_data/account_check.rs b/framework/scenario/src/scenario/model/account_data/account_check.rs index 2366941c02..dfc92e45ba 100644 --- a/framework/scenario/src/scenario/model/account_data/account_check.rs +++ b/framework/scenario/src/scenario/model/account_data/account_check.rs @@ -11,7 +11,7 @@ use crate::{ }, scenario_model::CheckEsdtData, }; -use std::collections::BTreeMap; +use std::collections::{btree_map, BTreeMap}; #[derive(Debug, Default, Clone)] pub struct CheckAccount { @@ -86,7 +86,10 @@ impl CheckAccount { self.esdt = CheckEsdtMap::Equal(new_check_esdt_map); }, CheckEsdtMap::Equal(check_esdt_map) => { - if check_esdt_map.contents.contains_key(&token_id) { + if let btree_map::Entry::Vacant(e) = check_esdt_map.contents.entry(token_id.clone()) + { + e.insert(CheckEsdt::Short(balance)); + } else { let prev_entry = check_esdt_map.contents.get_mut(&token_id).unwrap(); match prev_entry { CheckEsdt::Short(prev_balance_check) => *prev_balance_check = balance, @@ -115,35 +118,105 @@ impl CheckAccount { BigUintValue: From, T: TopEncode, { - let token_id = BytesKey::from(token_id_expr); + let insert_check_esdt = |map: &mut CheckEsdtMapContents| { + let token_id = BytesKey::from(token_id_expr); + let attributes_expr = if let Some(attributes_expr) = attributes_expr { + top_encode_to_vec_u8_or_panic(&attributes_expr) + } else { + Vec::::new() + }; - if let CheckEsdtMap::Unspecified = &self.esdt { let mut check_esdt = CheckEsdt::Full(CheckEsdtData::default()); + check_esdt.add_balance_and_attributes_check(nonce_expr, balance_expr, attributes_expr); - if let Some(attributes_expr) = attributes_expr { - check_esdt.add_balance_and_attributes_check( - nonce_expr, - balance_expr, - top_encode_to_vec_u8_or_panic(&attributes_expr), - ); - } else { - check_esdt.add_balance_and_attributes_check( - nonce_expr, - balance_expr, - Vec::::new(), - ); - } - - let mut new_esdt_map = BTreeMap::new(); - let _ = new_esdt_map.insert(token_id, check_esdt); - - let new_check_esdt_map = CheckEsdtMapContents { - contents: new_esdt_map, - other_esdts_allowed: true, - }; + map.contents.insert(token_id, check_esdt) + }; - self.esdt = CheckEsdtMap::Equal(new_check_esdt_map); - } + match &mut self.esdt { + CheckEsdtMap::Equal(map) => { + insert_check_esdt(map); + }, + _ => { + let mut new_map = CheckEsdtMapContents { + contents: BTreeMap::new(), + other_esdts_allowed: true, + }; + + insert_check_esdt(&mut new_map); + + self.esdt = CheckEsdtMap::Equal(new_map); + }, + }; + + self + } + + pub fn esdt_roles(mut self, token_id_expr: K, roles: Vec) -> Self + where + BytesKey: From, + { + let insert_check_esdt = |map: &mut CheckEsdtMapContents| { + let token_id = BytesKey::from(token_id_expr); + + map.contents + .entry(token_id) + .and_modify(|check_esdt| check_esdt.add_roles_check(roles.clone())) + .or_insert(CheckEsdt::Full(CheckEsdtData { + roles, + ..Default::default() + })); + }; + + match &mut self.esdt { + CheckEsdtMap::Equal(map) => { + insert_check_esdt(map); + }, + _ => { + let mut new_map = CheckEsdtMapContents { + contents: BTreeMap::new(), + other_esdts_allowed: true, + }; + + insert_check_esdt(&mut new_map); + + self.esdt = CheckEsdtMap::Equal(new_map); + }, + }; + + self + } + + pub fn esdt_nft_last_nonce(mut self, token_id_expr: K, last_nonce: u64) -> Self + where + BytesKey: From, + { + let insert_check_esdt = |map: &mut CheckEsdtMapContents| { + let token_id = BytesKey::from(token_id_expr); + + map.contents + .entry(token_id) + .and_modify(|check_esdt| check_esdt.add_last_nonce_check(last_nonce)) + .or_insert(CheckEsdt::Full(CheckEsdtData { + last_nonce: CheckValue::Equal(last_nonce.into()), + ..Default::default() + })); + }; + + match &mut self.esdt { + CheckEsdtMap::Equal(map) => { + insert_check_esdt(map); + }, + _ => { + let mut new_map = CheckEsdtMapContents { + contents: BTreeMap::new(), + other_esdts_allowed: true, + }; + + insert_check_esdt(&mut new_map); + + self.esdt = CheckEsdtMap::Equal(new_map); + }, + }; self } @@ -192,7 +265,7 @@ impl IntoRaw for CheckAccount { fn into_raw(self) -> CheckAccountRaw { CheckAccountRaw { comment: self.comment, - nonce: self.nonce.into_raw(), + nonce: self.nonce.into_raw_explicit(), balance: self.balance.into_raw(), esdt: self.esdt.into_raw(), username: self.username.into_raw(), diff --git a/framework/scenario/src/scenario/model/esdt_data/esdt_check.rs b/framework/scenario/src/scenario/model/esdt_data/esdt_check.rs index b268d1eb5f..a78f5b54de 100644 --- a/framework/scenario/src/scenario/model/esdt_data/esdt_check.rs +++ b/framework/scenario/src/scenario/model/esdt_data/esdt_check.rs @@ -62,6 +62,27 @@ impl CheckEsdt { } } + pub fn add_roles_check(&mut self, roles: Vec) { + self.convert_to_full(); + + if let CheckEsdt::Full(prev_esdt_check) = self { + prev_esdt_check.roles = roles; + } + } + + pub fn add_last_nonce_check(&mut self, nonce_expr: N) + where + U64Value: From, + { + let last_nonce = U64Value::from(nonce_expr); + + self.convert_to_full(); + + if let CheckEsdt::Full(prev_esdt_check) = self { + prev_esdt_check.last_nonce = CheckValue::Equal(last_nonce); + } + } + pub fn add_balance_check(&mut self, nonce_expr: N, balance_expr: V) where U64Value: InterpretableFrom, diff --git a/framework/scenario/src/scenario/model/esdt_data/esdt_data_check.rs b/framework/scenario/src/scenario/model/esdt_data/esdt_data_check.rs index facb5a4fe5..260904fd54 100644 --- a/framework/scenario/src/scenario/model/esdt_data/esdt_data_check.rs +++ b/framework/scenario/src/scenario/model/esdt_data/esdt_data_check.rs @@ -13,6 +13,7 @@ pub struct CheckEsdtData { pub instances: CheckEsdtInstances, pub last_nonce: CheckValue, pub frozen: CheckValue, + pub roles: Vec } impl InterpretableFrom for CheckEsdtData { @@ -21,6 +22,7 @@ impl InterpretableFrom for CheckEsdtData { instances: CheckEsdtInstances::interpret_from(from.instances, context), last_nonce: CheckValue::::interpret_from(from.last_nonce, context), frozen: CheckValue::::interpret_from(from.frozen, context), + roles: from.roles } } } @@ -30,7 +32,7 @@ impl IntoRaw for CheckEsdtData { CheckEsdtDataRaw { instances: self.instances.into_raw(), last_nonce: self.last_nonce.into_raw(), - roles: Vec::new(), + roles: self.roles, frozen: self.frozen.into_raw(), } } diff --git a/framework/scenario/src/scenario/model/transaction/tx_expect.rs b/framework/scenario/src/scenario/model/transaction/tx_expect.rs index 6f64a471f0..6d5ec5190b 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_expect.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_expect.rs @@ -150,7 +150,7 @@ impl IntoRaw for TxExpect { message: self.message.into_raw(), logs: self.logs.into_raw(), gas: self.gas.into_raw(), - refund: self.refund.into_raw(), + refund: self.refund.into_raw_explicit(), } } } diff --git a/framework/scenario/src/scenario/run_vm/check_state.rs b/framework/scenario/src/scenario/run_vm/check_state.rs index fae07c3591..10b1d0c9fa 100644 --- a/framework/scenario/src/scenario/run_vm/check_state.rs +++ b/framework/scenario/src/scenario/run_vm/check_state.rs @@ -124,8 +124,10 @@ pub fn check_account_esdt(address: &AddressKey, expected: &CheckEsdtMap, actual: match expected_value { CheckEsdt::Short(expected_balance) => { if expected_balance.value.is_zero() { + let actual_value_is_empty = + actual_value.is_empty() || actual_value.is_empty_with_roles(); assert!( - actual_value.is_empty(), + actual_value_is_empty, "No balance expected for ESDT token address: {}. token name: {}. nonce: {}.", address, bytes_to_string(key.value.as_slice()), diff --git a/framework/scenario/src/scenario/run_vm/set_state.rs b/framework/scenario/src/scenario/run_vm/set_state.rs index c09804ae1b..1fef3b634d 100644 --- a/framework/scenario/src/scenario/run_vm/set_state.rs +++ b/framework/scenario/src/scenario/run_vm/set_state.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::scenario::model::SetStateStep; use multiversx_chain_vm::{ @@ -18,51 +20,131 @@ impl ScenarioVMRunner { fn execute(state: &mut BlockchainState, set_state_step: &SetStateStep) { for (address, account) in set_state_step.accounts.iter() { - let storage = account + let update = match account.update { + Some(update) => update, + _ => false, + }; + + let old_account_data = state.accounts.get(&address.to_vm_address()); + if update && old_account_data.is_none() { + panic!( + "Called update flag on non-existent Address: {}", + &address.to_string() + ) + } + + let storage: HashMap, Vec> = account .storage .iter() .map(|(k, v)| (k.value.clone(), v.value.clone())) .collect(); - let esdt = AccountEsdt::new_from_raw_map( - account - .esdt - .iter() - .map(|(k, v)| (k.value.clone(), convert_mandos_esdt_to_world_mock(v))) - .collect(), - ); + + let storage = if update { + let mut old_storage = old_account_data.unwrap().storage.clone(); + + for (k, v) in storage.into_iter() { + old_storage + .entry(k) + .and_modify(|old_v| *old_v = v.clone()) + .or_insert(v); + } + + old_storage + } else { + storage + }; + + // TODO: Update flag not yet implemented for this. Implement when needed + let esdt = if !account.esdt.is_empty() { + AccountEsdt::new_from_raw_map( + account + .esdt + .iter() + .map(|(k, v)| (k.value.clone(), convert_mandos_esdt_to_world_mock(v))) + .collect(), + ) + } else if update { + old_account_data.unwrap().esdt.clone() + } else { + Default::default() + }; + + let nonce = match account.nonce.as_ref() { + Some(nonce) => nonce.value, + None => { + if update { + old_account_data.unwrap().nonce + } else { + Default::default() + } + }, + }; + + let egld_balance = match account.balance.as_ref() { + Some(balance) => balance.value.clone(), + None => { + if update { + old_account_data.unwrap().egld_balance.clone() + } else { + Default::default() + } + }, + }; + + let username = match account.username.as_ref() { + Some(bytes_value) => bytes_value.value.clone(), + None => { + if update { + old_account_data.unwrap().username.clone() + } else { + Default::default() + } + }, + }; + + let contract_path = match account.code.as_ref() { + Some(bytes_value) => Some(bytes_value.value.clone()), + None => { + if update { + old_account_data.unwrap().contract_path.clone() + } else { + Default::default() + } + }, + }; + + let contract_owner = match account.owner.as_ref() { + Some(address_value) => Some(address_value.to_vm_address()), + None => { + if update { + old_account_data.unwrap().contract_owner.clone() + } else { + Default::default() + } + }, + }; + + let developer_rewards = match account.developer_rewards.as_ref() { + Some(rewards) => rewards.value.clone(), + None => { + if update { + old_account_data.unwrap().developer_rewards.clone() + } else { + Default::default() + } + }, + }; state.validate_and_add_account(AccountData { address: address.to_vm_address(), - nonce: account - .nonce - .as_ref() - .map(|nonce| nonce.value) - .unwrap_or_default(), - egld_balance: account - .balance - .as_ref() - .map(|balance| balance.value.clone()) - .unwrap_or_default(), + nonce, + egld_balance, esdt, - username: account - .username - .as_ref() - .map(|bytes_value| bytes_value.value.clone()) - .unwrap_or_default(), + username, storage, - contract_path: account - .code - .as_ref() - .map(|bytes_value| bytes_value.value.clone()), - contract_owner: account - .owner - .as_ref() - .map(|address_value| address_value.to_vm_address()), - developer_rewards: account - .developer_rewards - .as_ref() - .map(|rewards| rewards.value.clone()) - .unwrap_or_default(), + contract_path, + contract_owner, + developer_rewards, }); } for new_address in set_state_step.new_addresses.iter() { diff --git a/framework/scenario/src/scenario/run_vm/vm_runner.rs b/framework/scenario/src/scenario/run_vm/vm_runner.rs index 605ce58df0..536fa9b70b 100644 --- a/framework/scenario/src/scenario/run_vm/vm_runner.rs +++ b/framework/scenario/src/scenario/run_vm/vm_runner.rs @@ -25,7 +25,9 @@ impl ScenarioVMRunner { impl ScenarioRunner for ScenarioVMRunner { fn run_external_steps(&mut self, _step: &ExternalStepsStep) { - panic!("cannot call directly as such") + // Ignored so we can write to trace + // TODO find a beter way + // panic!("cannot call directly as such") } fn run_set_state_step(&mut self, step: &SetStateStep) { diff --git a/framework/scenario/src/standalone/account_tool.rs b/framework/scenario/src/standalone/account_tool.rs index 877e75fc18..b760ca89e4 100644 --- a/framework/scenario/src/standalone/account_tool.rs +++ b/framework/scenario/src/standalone/account_tool.rs @@ -59,6 +59,7 @@ pub async fn retrieve_account_as_scenario_set_state( code: retrieve_code(account.code), owner: None, developer_rewards: None, + update: None, }, ); diff --git a/framework/scenario/src/whitebox_legacy/raw_converter.rs b/framework/scenario/src/whitebox_legacy/raw_converter.rs index 503f55be97..13fe1eaede 100644 --- a/framework/scenario/src/whitebox_legacy/raw_converter.rs +++ b/framework/scenario/src/whitebox_legacy/raw_converter.rs @@ -55,6 +55,7 @@ pub(crate) fn account_as_raw(acc: &AccountData) -> AccountRaw { storage: storage_raw, username: None, // TODO: Add if needed developer_rewards: developer_rewards_raw, + update: None, } } diff --git a/framework/scenario/tests/scenarios-io/example_normalized.scen.json b/framework/scenario/tests/scenarios-io/example_normalized.scen.json index 58faff2f25..e5f843a8d2 100644 --- a/framework/scenario/tests/scenarios-io/example_normalized.scen.json +++ b/framework/scenario/tests/scenarios-io/example_normalized.scen.json @@ -239,7 +239,8 @@ }, "+" ], - "gas": "0x1234" + "gas": "0x1234", + "refund": "*" } }, { @@ -272,7 +273,8 @@ "gasLimit": "0x100000" }, "expect": { - "status": "" + "status": "", + "refund": "*" } }, { @@ -288,7 +290,8 @@ }, "expect": { "out": [], - "status": "" + "status": "", + "refund": "*" } }, { @@ -325,7 +328,8 @@ }, "expect": { "out": [], - "status": "" + "status": "", + "refund": "*" } }, { @@ -363,7 +367,8 @@ }, "expect": { "out": [], - "status": "" + "status": "", + "refund": "*" } }, { @@ -390,6 +395,10 @@ { "nonce": "1023" } + ], + "roles": [ + "role1", + "role2" ] }, "str:5-SeveralNFTs": { @@ -402,6 +411,13 @@ "balance": "3" } ], + "roles": [ + "ESDTRoleLocalMint", + "ESDTRoleLocalBurn", + "ESDTRoleNFTCreate", + "ESDTRoleNFTAddQuantity", + "ESDTRoleNFTBurn" + ], "frozen": "false" }, "str:6-ZERO": "0", @@ -413,6 +429,7 @@ "owner": "*" }, "address:account_with_defaults": { + "nonce": "*", "esdt": "*", "storage": "*", "code": "*", @@ -440,12 +457,14 @@ "owner": "address:bob" }, "address:smart_contract_address_2": { + "nonce": "*", "storage": "*", "code": "*", "owner": "*", "asyncCallData": "``func@arg1@arg2" }, "str:account_with_defaults___________": { + "nonce": "*", "storage": "*", "code": "*", "owner": "*" diff --git a/sdk/scenario-format/src/serde_raw/account_data_raw/account_raw.rs b/sdk/scenario-format/src/serde_raw/account_data_raw/account_raw.rs index d9ee01c93d..7a792dd2e9 100644 --- a/sdk/scenario-format/src/serde_raw/account_data_raw/account_raw.rs +++ b/sdk/scenario-format/src/serde_raw/account_data_raw/account_raw.rs @@ -41,4 +41,8 @@ pub struct AccountRaw { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub developer_rewards: Option, + + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub update: Option, } diff --git a/sdk/scenario-format/src/serde_raw/value_raw_check_list.rs b/sdk/scenario-format/src/serde_raw/value_raw_check_list.rs index 25c445c126..329559a79c 100644 --- a/sdk/scenario-format/src/serde_raw/value_raw_check_list.rs +++ b/sdk/scenario-format/src/serde_raw/value_raw_check_list.rs @@ -21,6 +21,10 @@ impl CheckValueListRaw { pub fn is_unspecified(&self) -> bool { matches!(self, CheckValueListRaw::Unspecified) } + + pub fn as_star() -> Self { + Self::Star + } } impl Serialize for CheckValueListRaw { diff --git a/vm/src/tx_mock/tx_async_call_data.rs b/vm/src/tx_mock/tx_async_call_data.rs index f30cd9a3c2..9a89755bd4 100644 --- a/vm/src/tx_mock/tx_async_call_data.rs +++ b/vm/src/tx_mock/tx_async_call_data.rs @@ -27,7 +27,7 @@ pub fn async_call_tx_input(async_call: &AsyncCallTxData, call_type: CallType) -> func_name: async_call.endpoint_name.clone(), args: async_call.arguments.clone(), call_type, - gas_limit: 1000, + gas_limit: u64::MAX, gas_price: 0, tx_hash: async_call.tx_hash.clone(), ..Default::default() @@ -72,7 +72,8 @@ pub fn async_callback_tx_input( func_name: TxFunctionName::CALLBACK, args, call_type: CallType::AsyncCallback, - gas_limit: 1000, + gas_limit: u64::MAX, + gas_price: 0, tx_hash: async_data.tx_hash.clone(), callback_payments, diff --git a/vm/src/vm_hooks/vh_impl/vh_debug_api.rs b/vm/src/vm_hooks/vh_impl/vh_debug_api.rs index 004ab5cb82..798af540a4 100644 --- a/vm/src/vm_hooks/vh_impl/vh_debug_api.rs +++ b/vm/src/vm_hooks/vh_impl/vh_debug_api.rs @@ -151,7 +151,7 @@ impl VMHooksHandlerSource for DebugApiVMHooksHandler { esdt_values: Vec::new(), func_name: TxFunctionName::EMPTY, args, - gas_limit: 1000, + gas_limit: u64::MAX, gas_price: 0, tx_hash, ..Default::default() diff --git a/vm/src/world_mock/esdt_data.rs b/vm/src/world_mock/esdt_data.rs index 4d7093aa2e..327827aec3 100644 --- a/vm/src/world_mock/esdt_data.rs +++ b/vm/src/world_mock/esdt_data.rs @@ -25,6 +25,13 @@ impl EsdtData { && !self.frozen } + pub fn is_empty_with_roles(&self) -> bool { + self.instances.is_empty_esdt() + && self.last_nonce == 0 + && !self.roles.is_empty() + && !self.frozen + } + pub fn get_roles(&self) -> Vec> { self.roles.get() }