diff --git a/contracts/core/price-aggregator/src/lib.rs b/contracts/core/price-aggregator/src/lib.rs index 1dbed54848..9d9443dcb5 100644 --- a/contracts/core/price-aggregator/src/lib.rs +++ b/contracts/core/price-aggregator/src/lib.rs @@ -358,6 +358,7 @@ pub trait PriceAggregator: } #[view(getOracles)] + #[title("oracles")] fn get_oracles(&self) -> MultiValueEncoded { let mut result = MultiValueEncoded::new(); for key in self.oracle_status().keys() { diff --git a/contracts/core/wegld-swap/src/wegld.rs b/contracts/core/wegld-swap/src/wegld.rs index 4a38cab09e..23a4493494 100644 --- a/contracts/core/wegld-swap/src/wegld.rs +++ b/contracts/core/wegld-swap/src/wegld.rs @@ -55,12 +55,14 @@ pub trait EgldEsdtSwap: multiversx_sc_modules::pause::PauseModule { } #[view(getLockedEgldBalance)] + #[title("lockedEgldBalance")] fn get_locked_egld_balance(&self) -> BigUint { self.blockchain() .get_sc_balance(&EgldOrEsdtTokenIdentifier::egld(), 0) } #[view(getWrappedEgldTokenId)] + #[title("wrappedEgldTokenId")] #[storage_mapper("wrappedEgldTokenId")] fn wrapped_egld_token_id(&self) -> SingleValueMapper; } diff --git a/contracts/examples/adder/interact/src/basic_interact.rs b/contracts/examples/adder/interact/src/basic_interact.rs index a9769a82db..40e6a1c024 100644 --- a/contracts/examples/adder/interact/src/basic_interact.rs +++ b/contracts/examples/adder/interact/src/basic_interact.rs @@ -116,7 +116,6 @@ impl AdderInteract { .code(ADDER_CODE_PATH) .code_metadata(CodeMetadata::UPGRADEABLE) .returns(ReturnsNewBech32Address) - .prepare_async() .run() .await; @@ -183,7 +182,6 @@ impl AdderInteract { .from(&self.wallet_address) .to(self.state.current_adder_address()) .egld(NumExpr("0,050000000000000000")) - .prepare_async() .run() .await; } @@ -196,7 +194,6 @@ impl AdderInteract { .gas(6_000_000) .typed(adder_proxy::AdderProxy) .add(value) - .prepare_async() .run() .await; @@ -211,7 +208,6 @@ impl AdderInteract { .typed(adder_proxy::AdderProxy) .sum() .returns(ReturnsResultUnmanaged) - .prepare_async() .run() .await; @@ -237,7 +233,6 @@ impl AdderInteract { .code_metadata(CodeMetadata::UPGRADEABLE) .code(ADDER_CODE_PATH) .returns(ExpectError(code, msg)) - .prepare_async() .run() .await; @@ -253,7 +248,6 @@ impl AdderInteract { .upgrade(new_value) .code_metadata(CodeMetadata::UPGRADEABLE) .code(ADDER_CODE_PATH) - .prepare_async() .run() .await; @@ -264,7 +258,6 @@ impl AdderInteract { .typed(adder_proxy::AdderProxy) .sum() .returns(ReturnsResultUnmanaged) - .prepare_async() .run() .await; diff --git a/contracts/examples/crowdfunding-esdt/src/crowdfunding_esdt.rs b/contracts/examples/crowdfunding-esdt/src/crowdfunding_esdt.rs index 1dea493562..d47e256f08 100644 --- a/contracts/examples/crowdfunding-esdt/src/crowdfunding_esdt.rs +++ b/contracts/examples/crowdfunding-esdt/src/crowdfunding_esdt.rs @@ -55,6 +55,7 @@ pub trait Crowdfunding { } #[view(getCurrentFunds)] + #[title("currentFunds")] fn get_current_funds(&self) -> BigUint { let token = self.cf_token_identifier().get(); @@ -106,18 +107,22 @@ pub trait Crowdfunding { // storage #[view(getTarget)] + #[title("target")] #[storage_mapper("target")] fn target(&self) -> SingleValueMapper; #[view(getDeadline)] + #[title("deadline")] #[storage_mapper("deadline")] fn deadline(&self) -> SingleValueMapper; #[view(getDeposit)] + #[title("deposit")] #[storage_mapper("deposit")] fn deposit(&self, donor: &ManagedAddress) -> SingleValueMapper; #[view(getCrowdfundingTokenIdentifier)] + #[title("tokenIdentifier")] #[storage_mapper("tokenIdentifier")] fn cf_token_identifier(&self) -> SingleValueMapper; } diff --git a/contracts/examples/multisig/interact/src/multisig_interact.rs b/contracts/examples/multisig/interact/src/multisig_interact.rs index c21a772003..3eb44ed228 100644 --- a/contracts/examples/multisig/interact/src/multisig_interact.rs +++ b/contracts/examples/multisig/interact/src/multisig_interact.rs @@ -145,7 +145,6 @@ impl MultisigInteract { .code(&self.multisig_code) .gas(NumExpr("100,000,000")) .returns(ReturnsNewBech32Address) - .prepare_async() .run() .await; @@ -202,7 +201,6 @@ impl MultisigInteract { .from(&self.wallet_address) .to(self.state.current_multisig_address()) .egld(BigUint::from(50_000_000_000_000_000u64)) // 0,05 or 5 * 10^16 - .prepare_async() .run() .await; } @@ -220,7 +218,6 @@ impl MultisigInteract { .gas(gas_expr) .typed(multisig_proxy::MultisigProxy) .perform_action_endpoint(action_id) - .prepare_async() .run() .await; @@ -273,7 +270,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .quorum_reached(action_id) .returns(ReturnsResult) - .prepare_async() .run() .await } @@ -285,7 +281,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .signed(signer, action_id) .returns(ReturnsResult) - .prepare_async() .run() .await } @@ -340,7 +335,6 @@ impl MultisigInteract { .gas(NumExpr("30,000,000")) .typed(multisig_proxy::MultisigProxy) .dns_register(dns_address, name) - .prepare_async() .run() .await; @@ -355,7 +349,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .quorum() .returns(ReturnsResult) - .prepare_async() .run() .await; @@ -370,7 +363,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .num_board_members() .returns(ReturnsResult) - .prepare_async() .run() .await; diff --git a/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs b/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs index 2efda8e4d6..e46411e776 100644 --- a/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs +++ b/contracts/examples/multisig/interact/src/multisig_interact_nfts.rs @@ -50,7 +50,6 @@ impl MultisigInteract { .argument(&0u32), ) .returns(ReturnsResult) - .prepare_async() .run() .await; @@ -75,7 +74,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .perform_action_endpoint(action_id) .returns(ReturnsNewTokenIdentifier) - .prepare_async() .run() .await; self.collection_token_identifier = new_token_id.to_string(); @@ -102,7 +100,6 @@ impl MultisigInteract { .argument(&COLLECTION_TICKER), ) .returns(ReturnsResult) - .prepare_async() .run() .await; @@ -127,7 +124,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .perform_action_endpoint(action_id) .returns(ReturnsNewTokenIdentifier) - .prepare_async() .run() .await; self.collection_token_identifier = new_token_id; @@ -156,7 +152,6 @@ impl MultisigInteract { .argument(&"ESDTRoleNFTCreate"), ) .returns(ReturnsResult) - .prepare_async() .run() .await; diff --git a/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs b/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs index 7774bc02d4..2ed1a93b30 100644 --- a/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs +++ b/contracts/examples/multisig/interact/src/multisig_interact_wegld.rs @@ -56,7 +56,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .propose_async_call(&self.config.wegld_address, WRAP_AMOUNT, function_call) .returns(ReturnsResult) - .prepare_async() .run() .await; @@ -72,7 +71,6 @@ impl MultisigInteract { .typed(wegld_proxy::EgldEsdtSwapProxy) .wrapped_egld_token_id() .returns(ReturnsResult) - .prepare_async() .run() .await; @@ -104,7 +102,6 @@ impl MultisigInteract { .typed(multisig_proxy::MultisigProxy) .propose_async_call(normalized_to, 0u64, normalized_data) .returns(ReturnsResult) - .prepare_async() .run() .await; diff --git a/contracts/examples/ping-pong-egld/dapp/src/interactor.rs b/contracts/examples/ping-pong-egld/dapp/src/interactor.rs index f8841141c0..ba5ca3f3f8 100644 --- a/contracts/examples/ping-pong-egld/dapp/src/interactor.rs +++ b/contracts/examples/ping-pong-egld/dapp/src/interactor.rs @@ -1,13 +1,10 @@ -use imports::{Address, Bech32Address, BytesValue}; -use multiversx_sc_snippets_dapp::*; +use multiversx_sc_snippets_dapp::imports::*; use serde::{Deserialize, Serialize}; -const GATEWAY: &str = sdk::core::gateway::DEVNET_GATEWAY; +const GATEWAY: &str = multiversx_sc_snippets_dapp::sdk::core::gateway::DEVNET_GATEWAY; const CONTRACT_ADDRESS: &str = "erd1qqqqqqqqqqqqqpgq6tqvj5f59xrgxwrtwy30elgpu7l4zrv6d8ssnjdwxq"; const PING_PONG_CODE: &[u8] = include_bytes!("../ping-pong-egld.wasm"); -use multiversx_sc_snippets_dapp::imports::*; - #[derive(Debug, Default, Serialize, Deserialize)] pub struct Config { gateway: String, diff --git a/contracts/examples/ping-pong-egld/dapp/src/requests/transaction.rs b/contracts/examples/ping-pong-egld/dapp/src/requests/transaction.rs index 3765ee63d6..a7629ea8e9 100644 --- a/contracts/examples/ping-pong-egld/dapp/src/requests/transaction.rs +++ b/contracts/examples/ping-pong-egld/dapp/src/requests/transaction.rs @@ -1,8 +1,5 @@ use crate::interactor::ContractInteract; -use multiversx_sc_snippets_dapp::imports::{ - Bech32Address, BigUint, IgnoreValue, InteractorPrepareAsync, OptionalValue, ReturnsMessage, - ReturnsNewBech32Address, ReturnsStatus, -}; +use multiversx_sc_snippets_dapp::imports::*; use super::proxy; @@ -29,7 +26,6 @@ pub async fn deploy_sc() -> Result { .returns(ReturnsNewBech32Address) .returns(ReturnsStatus) .returns(ReturnsMessage) - .prepare_async() .run() .await; @@ -57,7 +53,6 @@ pub async fn ping() -> Result { .typed(proxy::PingPongProxy) .ping(_data) .egld(BigUint::from(amount)) - .prepare_async() .run() .await; diff --git a/contracts/feature-tests/abi-tester/abi_tester_expected_main.abi.json b/contracts/feature-tests/abi-tester/abi_tester_expected_main.abi.json index 60edcce7f1..8748ff67aa 100644 --- a/contracts/feature-tests/abi-tester/abi_tester_expected_main.abi.json +++ b/contracts/feature-tests/abi-tester/abi_tester_expected_main.abi.json @@ -110,6 +110,7 @@ }, { "name": "multi_result_3", + "title": "result-3", "mutability": "mutable", "inputs": [], "outputs": [ diff --git a/contracts/feature-tests/abi-tester/src/abi_tester.rs b/contracts/feature-tests/abi-tester/src/abi_tester.rs index f5ce735a8c..9405fa28a0 100644 --- a/contracts/feature-tests/abi-tester/src/abi_tester.rs +++ b/contracts/feature-tests/abi-tester/src/abi_tester.rs @@ -58,6 +58,7 @@ pub trait AbiTester { fn take_managed_type(&self, _arg: AbiManagedType) {} #[endpoint] + #[title("result-3")] #[output_name("multi-result-1")] #[output_name("multi-result-2")] #[output_name("multi-result-3")] diff --git a/contracts/feature-tests/basic-features/interact/src/bf_interact.rs b/contracts/feature-tests/basic-features/interact/src/bf_interact.rs index b971818f2b..57d5e9bfe0 100644 --- a/contracts/feature-tests/basic-features/interact/src/bf_interact.rs +++ b/contracts/feature-tests/basic-features/interact/src/bf_interact.rs @@ -86,7 +86,6 @@ impl BasicFeaturesInteract { .code(&self.code_expr) .gas(NumExpr("4,000,000")) .returns(ReturnsNewBech32Address) - .prepare_async() .run() .await; @@ -103,7 +102,6 @@ impl BasicFeaturesInteract { .gas(NumExpr("600,000,000")) .typed(basic_features_proxy::BasicFeaturesProxy) .store_bytes(value) - .prepare_async() .run() .await; @@ -118,7 +116,6 @@ impl BasicFeaturesInteract { .typed(basic_features_proxy::BasicFeaturesProxy) .load_bytes() .returns(ReturnsResult) - .prepare_async() .run() .await; diff --git a/contracts/feature-tests/composability/interact/src/call_tree_calling_functions.rs b/contracts/feature-tests/composability/interact/src/call_tree_calling_functions.rs index 0cb35bdbec..0bd8797f05 100644 --- a/contracts/feature-tests/composability/interact/src/call_tree_calling_functions.rs +++ b/contracts/feature-tests/composability/interact/src/call_tree_calling_functions.rs @@ -124,7 +124,6 @@ impl ComposabilityInteract { .to(&root_addr) .typed(forwarder_queue_proxy::ForwarderQueueProxy) .forward_queued_calls() - .prepare_async() .run() .await; diff --git a/contracts/feature-tests/scenario-tester/scenarios/interactor_trace.scen.json b/contracts/feature-tests/scenario-tester/scenarios/interactor_trace.scen.json index 07fd7f68ca..b5d3ed5b15 100644 --- a/contracts/feature-tests/scenario-tester/scenarios/interactor_trace.scen.json +++ b/contracts/feature-tests/scenario-tester/scenarios/interactor_trace.scen.json @@ -36,7 +36,9 @@ "gasLimit": "70,000,000" }, "expect": { - "out": [], + "out": [ + "str:init-result" + ], "status": "0" } }, diff --git a/contracts/feature-tests/scenario-tester/scenarios/st-adder.scen.json b/contracts/feature-tests/scenario-tester/scenarios/st-adder.scen.json index 94ba305ed2..c2e6570007 100644 --- a/contracts/feature-tests/scenario-tester/scenarios/st-adder.scen.json +++ b/contracts/feature-tests/scenario-tester/scenarios/st-adder.scen.json @@ -32,7 +32,9 @@ "gasPrice": "0" }, "expect": { - "out": [], + "out": [ + "str:init-result" + ], "status": "", "logs": "*", "gas": "*", diff --git a/contracts/feature-tests/scenario-tester/src/lib.rs b/contracts/feature-tests/scenario-tester/src/lib.rs index 97c352adeb..ba80b21abd 100644 --- a/contracts/feature-tests/scenario-tester/src/lib.rs +++ b/contracts/feature-tests/scenario-tester/src/lib.rs @@ -12,9 +12,11 @@ pub trait ScenarioTester { #[storage_mapper("sum")] fn sum(&self) -> SingleValueMapper; + /// Return value for testing reasons. #[init] - fn init(&self, initial_value: BigUint) { + fn init(&self, initial_value: BigUint) -> &'static str { self.sum().set(initial_value); + "init-result" } #[upgrade] @@ -38,4 +40,9 @@ pub trait ScenarioTester { let value_plus_one = &value + 1u32; (value, value_plus_one).into() } + + #[view] + fn sc_panic(&self) { + sc_panic!("sc_panic! example"); + } } diff --git a/contracts/feature-tests/scenario-tester/src/scenario_tester_proxy.rs b/contracts/feature-tests/scenario-tester/src/scenario_tester_proxy.rs index 66976893c2..25eb71225c 100644 --- a/contracts/feature-tests/scenario-tester/src/scenario_tester_proxy.rs +++ b/contracts/feature-tests/scenario-tester/src/scenario_tester_proxy.rs @@ -43,12 +43,13 @@ where From: TxFrom, Gas: TxGas, { + /// Return value for testing reasons. pub fn init< Arg0: ProxyArg>, >( self, initial_value: Arg0, - ) -> TxTypedDeploy { + ) -> TxTypedDeploy { self.wrapped_tx .payment(NotPayable) .raw_deploy() @@ -139,4 +140,13 @@ where .argument(&value) .original_result() } + + pub fn sc_panic( + self, + ) -> TxTypedCall { + self.wrapped_tx + .payment(NotPayable) + .raw_call("sc_panic") + .original_result() + } } diff --git a/contracts/feature-tests/scenario-tester/tests/st_blackbox_raw_steps_test.rs b/contracts/feature-tests/scenario-tester/tests/st_blackbox_raw_steps_test.rs index 68ac9b8d39..adc3ffa34e 100644 --- a/contracts/feature-tests/scenario-tester/tests/st_blackbox_raw_steps_test.rs +++ b/contracts/feature-tests/scenario-tester/tests/st_blackbox_raw_steps_test.rs @@ -25,7 +25,7 @@ fn scenario_tester_blackbox_raw() { .from("address:owner") .code(scenario_tester_code) .argument("5") - .expect(TxExpect::ok().no_result()), + .expect(TxExpect::ok().result("str:init-result")), ) .sc_query( ScQueryStep::new() 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 d3f78692e8..5c67ce2a3b 100644 --- a/contracts/feature-tests/scenario-tester/tests/st_blackbox_test.rs +++ b/contracts/feature-tests/scenario-tester/tests/st_blackbox_test.rs @@ -1,4 +1,4 @@ -use multiversx_sc_scenario::imports::*; +use multiversx_sc_scenario::{imports::*, scenario_model::TxResponseStatus}; use scenario_tester::*; @@ -286,3 +286,99 @@ fn st_blackbox_tx_hash() { assert_eq!(tx_hash.as_array(), &[22u8; 32]); } + +#[test] +fn st_blackbox_returns_result_or_error() { + 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(); + + // deploy + let (result, check_tx_hash) = world + .tx() + .from(OWNER_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .init(5u32) + .code(CODE_PATH) + .new_address(ST_ADDRESS) + .tx_hash([33u8; 32]) + .returns( + ReturnsHandledOrError::new() + .returns(ReturnsNewAddress) + .returns(ReturnsResultAs::::new()) + .returns(ReturnsTxHash), + ) + .returns(ReturnsTxHash) + .run(); + + assert_eq!(check_tx_hash.as_array(), &[33u8; 32]); + let (new_address, out_value, also_check_tx_hash) = result.unwrap(); + assert_eq!(new_address, ST_ADDRESS.to_address()); + assert_eq!(out_value, "init-result"); + assert_eq!(also_check_tx_hash.as_array(), &[33u8; 32]); + + // query - ok + let result = world + .query() + .to(ST_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .sum() + .returns(ReturnsHandledOrError::new().returns(ReturnsResultUnmanaged)) + .run(); + assert_eq!(result, Ok(RustBigUint::from(5u32))); + + // query - error + let result = world + .query() + .to(ST_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .sc_panic() + .returns(ReturnsHandledOrError::new()) + .run(); + + assert_eq!( + result, + Err(TxResponseStatus::new( + ReturnCode::UserError, + "sc_panic! example" + )) + ); + + // call - ok + let result = world + .tx() + .from(OWNER_ADDRESS) + .to(ST_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .add(1u32) + .returns(ReturnsHandledOrError::new()) + .run(); + + assert_eq!(result, Ok(())); + + // call - error + let result = world + .tx() + .from(OWNER_ADDRESS) + .to(ST_ADDRESS) + .typed(scenario_tester_proxy::ScenarioTesterProxy) + .sc_panic() + .returns(ReturnsHandledOrError::new()) + .run(); + + assert_eq!( + result, + Err(TxResponseStatus::new( + ReturnCode::UserError, + "sc_panic! example" + )) + ); +} diff --git a/contracts/feature-tests/scenario-tester/tests/st_blackbox_upgrade_test.rs b/contracts/feature-tests/scenario-tester/tests/st_blackbox_upgrade_test.rs index 3242739239..2fd12feaec 100644 --- a/contracts/feature-tests/scenario-tester/tests/st_blackbox_upgrade_test.rs +++ b/contracts/feature-tests/scenario-tester/tests/st_blackbox_upgrade_test.rs @@ -29,7 +29,7 @@ fn st_blackbox_upgrade() { .code(&st_code) .argument("5") .gas_limit("5,000,000") - .expect(TxExpect::ok().no_result()), + .expect(TxExpect::ok().result("str:init-result")), ) .sc_call( ScCallStep::new() diff --git a/contracts/feature-tests/scenario-tester/wasm/src/lib.rs b/contracts/feature-tests/scenario-tester/wasm/src/lib.rs index 7976698233..b1360c1d95 100644 --- a/contracts/feature-tests/scenario-tester/wasm/src/lib.rs +++ b/contracts/feature-tests/scenario-tester/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 4 +// Endpoints: 5 // Async Callback (empty): 1 -// Total number of exported functions: 7 +// Total number of exported functions: 8 #![no_std] @@ -24,6 +24,7 @@ multiversx_sc_wasm_adapter::endpoints! { add => add multi_param => multi_param multi_return => multi_return + sc_panic => sc_panic ) } diff --git a/framework/base/src/abi/endpoint_abi.rs b/framework/base/src/abi/endpoint_abi.rs index 03e098d0e9..8e96554d84 100644 --- a/framework/base/src/abi/endpoint_abi.rs +++ b/framework/base/src/abi/endpoint_abi.rs @@ -1,5 +1,6 @@ use super::*; use alloc::{ + borrow::ToOwned, string::{String, ToString}, vec::Vec, }; @@ -42,6 +43,7 @@ pub struct EndpointAbi { pub docs: Vec, pub name: String, pub rust_method_name: String, + pub title: Option, pub only_owner: bool, pub only_admin: bool, pub labels: Vec, @@ -55,37 +57,64 @@ pub struct EndpointAbi { impl EndpointAbi { /// Used in code generation. - /// - /// TODO: replace with builder pattern to gt rid of the too many arguments. - #[allow(clippy::too_many_arguments)] pub fn new( - docs: &[&str], name: &str, rust_method_name: &str, - only_owner: bool, - only_admin: bool, mutability: EndpointMutabilityAbi, endpoint_type: EndpointTypeAbi, - payable_in_tokens: &[&str], - labels: &[&str], - allow_multiple_var_args: bool, ) -> Self { EndpointAbi { - docs: docs.iter().map(|s| s.to_string()).collect(), + docs: Vec::new(), name: name.to_string(), rust_method_name: rust_method_name.to_string(), - only_owner, - only_admin, - labels: labels.iter().map(|s| s.to_string()).collect(), + only_owner: false, + only_admin: false, + labels: Vec::new(), endpoint_type, mutability, - payable_in_tokens: payable_in_tokens.iter().map(|s| s.to_string()).collect(), + payable_in_tokens: Vec::new(), + title: None, inputs: Vec::new(), outputs: Vec::new(), - allow_multiple_var_args, + allow_multiple_var_args: false, } } + pub fn with_docs(mut self, doc_line: &str) -> Self { + self.docs.push(doc_line.to_owned()); + self + } + + pub fn with_title(mut self, title: &str) -> Self { + self.title = Some(title.to_owned()); + self + } + + pub fn with_only_owner(mut self) -> Self { + self.only_owner = true; + self + } + + pub fn with_only_admin(mut self) -> Self { + self.only_admin = true; + self + } + + pub fn with_allow_multiple_var_args(mut self) -> Self { + self.allow_multiple_var_args = true; + self + } + + pub fn with_label(mut self, label: &str) -> Self { + self.labels.push(label.to_owned()); + self + } + + pub fn with_payable_token(mut self, token: &str) -> Self { + self.payable_in_tokens.push(token.to_owned()); + self + } + pub fn add_input(&mut self, arg_name: &str) { self.inputs.push(InputAbi { arg_name: arg_name.to_string(), diff --git a/framework/base/src/external_view_contract.rs b/framework/base/src/external_view_contract.rs index 2b01176176..4388e74618 100644 --- a/framework/base/src/external_view_contract.rs +++ b/framework/base/src/external_view_contract.rs @@ -32,21 +32,14 @@ where /// The definition for the external view pub fn external_view_contract_constructor_abi() -> EndpointAbi { let mut endpoint_abi = EndpointAbi::new( - &[ - "The external view init prepares a contract that looks in another contract's storage.", - "It takes a single argument, the other contract's address", - "You won't find this constructors' definition in the contract, it gets injected automatically by the framework. See `multiversx_sc::external_view_contract`.", - ], "init", EXTERNAL_VIEW_CONSTRUCTOR_FLAG, - false, - false, EndpointMutabilityAbi::Mutable, EndpointTypeAbi::Init, - &[], - &[], - false - ); + ) + .with_docs("The external view init prepares a contract that looks in another contract's storage.") + .with_docs("It takes a single argument, the other contract's address") + .with_docs("You won't find this constructors' definition in the contract, it gets injected automatically by the framework. See `multiversx_sc::external_view_contract`."); endpoint_abi.inputs.push(InputAbi { arg_name: "target_contract_address".to_string(), type_names: crate::types::heap::Address::type_names(), diff --git a/framework/derive/src/generate/abi_gen.rs b/framework/derive/src/generate/abi_gen.rs index 5d5a73934c..0aa70fbc06 100644 --- a/framework/derive/src/generate/abi_gen.rs +++ b/framework/derive/src/generate/abi_gen.rs @@ -15,6 +15,27 @@ fn generate_endpoint_snippet( ) -> proc_macro2::TokenStream { let endpoint_docs = &m.docs; let rust_method_name = m.name.to_string(); + let title_tokens = m + .title + .as_ref() + .map(|title| quote! { .with_title(#title) }) + .unwrap_or_default(); + let only_owner_tokens = if only_owner { + quote! { .with_only_owner() } + } else { + quote! {} + }; + let only_admin_tokens = if only_admin { + quote! { .with_only_admin() } + } else { + quote! {} + }; + let allow_multiple_var_args_tokens = if allow_multiple_var_args { + quote! { .with_allow_multiple_var_args() } + } else { + quote! {} + }; + let payable_in_tokens = m.payable_metadata().abi_strings(); let input_snippets: Vec = m @@ -55,17 +76,20 @@ fn generate_endpoint_snippet( quote! { let mut endpoint_abi = multiversx_sc::abi::EndpointAbi::new( - &[ #(#endpoint_docs),* ], #endpoint_name, #rust_method_name, - #only_owner, - #only_admin, #mutability_tokens, #endpoint_type_tokens, - &[ #(#payable_in_tokens),* ], - &[ #(#label_names),* ], - #allow_multiple_var_args, - ); + ) + #(.with_docs(#endpoint_docs))* + #title_tokens + #only_owner_tokens + #only_admin_tokens + #(.with_label(#label_names))* + #(.with_payable_token(#payable_in_tokens))* + #allow_multiple_var_args_tokens + ; + #(#input_snippets)* #output_snippet } diff --git a/framework/derive/src/model/method.rs b/framework/derive/src/model/method.rs index 22acf8eb78..8a4e8ec5eb 100644 --- a/framework/derive/src/model/method.rs +++ b/framework/derive/src/model/method.rs @@ -40,6 +40,7 @@ pub struct Method { pub generics: syn::Generics, pub unprocessed_attributes: Vec, pub method_args: Vec, + pub title: Option, pub output_names: Vec, pub label_names: Vec, pub return_type: syn::ReturnType, diff --git a/framework/derive/src/parse/attributes/mod.rs b/framework/derive/src/parse/attributes.rs similarity index 93% rename from framework/derive/src/parse/attributes/mod.rs rename to framework/derive/src/parse/attributes.rs index e58c1a912e..a5f9906db9 100644 --- a/framework/derive/src/parse/attributes/mod.rs +++ b/framework/derive/src/parse/attributes.rs @@ -11,7 +11,7 @@ mod trait_prop_names; mod util; pub use argument_attr::*; -pub use doc_attr::{extract_doc, extract_macro_attributes, OutputNameAttribute}; +pub use doc_attr::{extract_doc, extract_macro_attributes, OutputNameAttribute, TitleAttribute}; pub use endpoint_attr::*; pub use event_attr::*; pub use label_attr::*; diff --git a/framework/derive/src/parse/attributes/attr_names.rs b/framework/derive/src/parse/attributes/attr_names.rs index 29784fc1c3..5a976e185c 100644 --- a/framework/derive/src/parse/attributes/attr_names.rs +++ b/framework/derive/src/parse/attributes/attr_names.rs @@ -2,6 +2,7 @@ pub(super) static ATTR_PAYABLE: &str = "payable"; pub(super) static ATTR_ONLY_OWNER: &str = "only_owner"; pub(super) static ATTR_ONLY_ADMIN: &str = "only_admin"; pub(super) static ATTR_ONLY_USER_ACCOUNT: &str = "only_user_account"; +pub(super) static ATTR_TITLE: &str = "title"; pub(super) static ATTR_OUTPUT_NAME: &str = "output_name"; pub(super) static ATTR_PAYMENT: &str = "payment"; // synonymous with `payment_amount` pub(super) static ATTR_PAYMENT_AMOUNT: &str = "payment_amount"; diff --git a/framework/derive/src/parse/attributes/doc_attr.rs b/framework/derive/src/parse/attributes/doc_attr.rs index 5c5ed4d41b..e9485d6368 100644 --- a/framework/derive/src/parse/attributes/doc_attr.rs +++ b/framework/derive/src/parse/attributes/doc_attr.rs @@ -81,3 +81,13 @@ impl OutputNameAttribute { }) } } + +pub struct TitleAttribute { + pub title: String, +} + +impl TitleAttribute { + pub fn parse(attr: &syn::Attribute) -> Option { + is_attr_one_string_arg(attr, ATTR_TITLE).map(|arg_str| TitleAttribute { title: arg_str }) + } +} diff --git a/framework/derive/src/parse/endpoint_parse.rs b/framework/derive/src/parse/endpoint_parse.rs index 5edc30ad08..26fb612ea6 100644 --- a/framework/derive/src/parse/endpoint_parse.rs +++ b/framework/derive/src/parse/endpoint_parse.rs @@ -8,7 +8,7 @@ use super::{ is_allow_multiple_var_args, is_callback_raw, is_init, is_only_admin, is_only_owner, is_only_user_account, is_upgrade, CallbackAttribute, EndpointAttribute, ExternalViewAttribute, LabelAttribute, OutputNameAttribute, PromisesCallbackAttribute, - ViewAttribute, + TitleAttribute, ViewAttribute, }, MethodAttributesPass1, }; @@ -224,6 +224,18 @@ pub fn process_output_names_attribute(attr: &syn::Attribute, method: &mut Method .is_some() } +pub fn process_title_attribute(attr: &syn::Attribute, method: &mut Method) -> bool { + TitleAttribute::parse(attr) + .map(|title_attr| { + assert!( + method.title.is_none(), + "only one title attribute allowed per method" + ); + method.title = Some(title_attr.title); + }) + .is_some() +} + pub fn process_label_names_attribute(attr: &syn::Attribute, method: &mut Method) -> bool { LabelAttribute::parse(attr) .map(|label_attr| { diff --git a/framework/derive/src/parse/method_parse.rs b/framework/derive/src/parse/method_parse.rs index 481638068a..e299912cdd 100644 --- a/framework/derive/src/parse/method_parse.rs +++ b/framework/derive/src/parse/method_parse.rs @@ -13,7 +13,7 @@ use super::{ process_init_attribute, process_label_names_attribute, process_only_admin_attribute, process_only_owner_attribute, process_only_user_account_attribute, process_output_names_attribute, process_payable_attribute, process_promises_callback_attribute, - process_upgrade_attribute, process_view_attribute, + process_title_attribute, process_upgrade_attribute, process_view_attribute, }; pub struct MethodAttributesPass1 { pub method_name: String, @@ -56,6 +56,7 @@ pub fn process_method(m: &syn::TraitItemFn, trait_attributes: &TraitProperties) generics: m.sig.generics.clone(), unprocessed_attributes: Vec::new(), method_args, + title: None, output_names: Vec::new(), label_names: Vec::new(), return_type: m.sig.output.clone(), @@ -131,6 +132,7 @@ fn process_attribute_second_pass( || process_storage_mapper_from_address_attribute(attr, method) || process_storage_is_empty_attribute(attr, method) || process_storage_clear_attribute(attr, method) + || process_title_attribute(attr, method) || process_output_names_attribute(attr, method) || process_label_names_attribute(attr, method) } diff --git a/framework/meta-lib/src/abi_json/endpoint_abi_json.rs b/framework/meta-lib/src/abi_json/endpoint_abi_json.rs index b6bd041713..0537718e4a 100644 --- a/framework/meta-lib/src/abi_json/endpoint_abi_json.rs +++ b/framework/meta-lib/src/abi_json/endpoint_abi_json.rs @@ -93,6 +93,10 @@ pub struct EndpointAbiJson { pub docs: Vec, pub name: String, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + #[serde(rename = "onlyOwner")] #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] @@ -128,6 +132,7 @@ impl From<&EndpointAbi> for EndpointAbiJson { EndpointAbiJson { docs: abi.docs.iter().map(|d| d.to_string()).collect(), name: abi.name.to_string(), + title: abi.title.clone(), only_owner: if abi.only_owner { Some(true) } else { None }, only_admin: if abi.only_admin { Some(true) } else { None }, mutability: match abi.mutability { diff --git a/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs b/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs index d450decd82..b2b5a23d5b 100644 --- a/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs +++ b/framework/meta-lib/src/contract/generate_snippets/snippet_sc_functions_gen.rs @@ -65,7 +65,7 @@ fn write_deploy_method_impl(file: &mut File, init_abi: &EndpointAbi, name: &Stri .init({}) .code(&self.contract_code) .returns(ReturnsNewAddress) - .prepare_async() + .run() .await; let new_address_bech32 = bech32::encode(&new_address); @@ -101,7 +101,7 @@ fn write_upgrade_endpoint_impl(file: &mut File, upgrade_abi: &EndpointAbi, name: .code(&self.contract_code) .code_metadata(CodeMetadata::UPGRADEABLE) .returns(ReturnsNewAddress) - .prepare_async() + .run() .await; @@ -214,7 +214,7 @@ fn write_contract_call(file: &mut File, endpoint_abi: &EndpointAbi, name: &Strin .typed(proxy::{}Proxy) .{}({}){} .returns(ReturnsResultUnmanaged) - .prepare_async() + .run() .await; @@ -237,7 +237,7 @@ fn write_contract_query(file: &mut File, endpoint_abi: &EndpointAbi, name: &Stri .typed(proxy::{}Proxy) .{}({}) .returns(ReturnsResultUnmanaged) - .prepare_async() + .run() .await; diff --git a/framework/meta-lib/src/contract/sc_config/wasm_build.rs b/framework/meta-lib/src/contract/sc_config/wasm_build.rs index 5b318da9a0..17312dbb04 100644 --- a/framework/meta-lib/src/contract/sc_config/wasm_build.rs +++ b/framework/meta-lib/src/contract/sc_config/wasm_build.rs @@ -130,13 +130,21 @@ impl ContractVariant { fn extract_wasm_info(&self, build_args: &BuildArgs, output_path: &str) -> WasmInfo { let output_wasm_path = format!("{output_path}/{}", self.wasm_output_name(build_args)); + let abi = ContractAbiJson::from(&self.abi); + let mut view_endpoints: Vec<&str> = Vec::new(); + for endpoint in &abi.endpoints { + if let crate::abi_json::EndpointMutabilityAbiJson::Readonly = endpoint.mutability { + view_endpoints.push(&endpoint.name); + } + } + if !build_args.extract_imports { return WasmInfo::extract_wasm_info( &output_wasm_path, build_args.extract_imports, &self.settings.check_ei, - ) - .expect("error occured while extracting imports from .wasm "); + view_endpoints, + ); } let output_imports_json_path = format!( @@ -146,9 +154,12 @@ impl ContractVariant { ); print_extract_imports(&output_imports_json_path); - let wasm_data = - WasmInfo::extract_wasm_info(&output_wasm_path, true, &self.settings.check_ei) - .expect("error occured while extracting imports from .wasm "); + let wasm_data = WasmInfo::extract_wasm_info( + &output_wasm_path, + true, + &self.settings.check_ei, + view_endpoints, + ); write_imports_output( output_imports_json_path.as_str(), diff --git a/framework/meta-lib/src/tools/report_creator.rs b/framework/meta-lib/src/tools/report_creator.rs index 9555814075..c45739c594 100644 --- a/framework/meta-lib/src/tools/report_creator.rs +++ b/framework/meta-lib/src/tools/report_creator.rs @@ -7,3 +7,13 @@ pub struct ReportCreator { } impl ReportCreator {} + +impl Default for ReportCreator { + fn default() -> Self { + ReportCreator { + path: String::new(), + has_allocator: false, + has_panic: PanicReport::None, + } + } +} diff --git a/framework/meta-lib/src/tools/wasm_extractor.rs b/framework/meta-lib/src/tools/wasm_extractor.rs index 733ca3fad0..eba84b4ada 100644 --- a/framework/meta-lib/src/tools/wasm_extractor.rs +++ b/framework/meta-lib/src/tools/wasm_extractor.rs @@ -1,22 +1,38 @@ use colored::Colorize; -use std::fs; +use std::{ + collections::{HashMap, HashSet}, + fs, +}; use wasmparser::{ - BinaryReaderError, DataSectionReader, FunctionBody, ImportSectionReader, Operator, Parser, - Payload, + BinaryReaderError, DataSectionReader, ExportSectionReader, FunctionBody, ImportSectionReader, + Operator, Parser, Payload, }; use crate::ei::EIVersion; -use super::{panic_report::PanicReport, report_creator::ReportCreator}; +use super::report_creator::ReportCreator; + +type CallGraph = HashMap>; const ERROR_FAIL_ALLOCATOR: &[u8; 27] = b"memory allocation forbidden"; +const WRITE_OP: &[&str] = &[ + "mBufferStorageStore", + "storageStore", + "int64storageStore", + "bigIntStorageStoreUnsigned", + "smallIntStorageStoreUnsigned", + "smallIntStorageStoreSigned", +]; +#[derive(Default)] pub struct WasmInfo { pub imports: Vec, pub ei_check: bool, pub memory_grow_flag: bool, - pub has_format: bool, pub report: ReportCreator, + pub call_graph: CallGraph, + pub write_index_functions: HashSet, + pub view_endpoints: HashMap, } impl WasmInfo { @@ -24,61 +40,146 @@ impl WasmInfo { output_wasm_path: &str, extract_imports_enabled: bool, check_ei: &Option, - ) -> Result { + view_endpoints: Vec<&str>, + ) -> WasmInfo { let wasm_data = fs::read(output_wasm_path) .expect("error occured while extracting information from .wasm: file not found"); - populate_wasm_info( + let wasm_info = populate_wasm_info( output_wasm_path.to_string(), wasm_data, extract_imports_enabled, check_ei, - ) + view_endpoints, + ); + + wasm_info.expect("error occured while extracting information from .wasm file") + } + + fn create_call_graph(&mut self, body: FunctionBody) { + let mut instructions_reader = body + .get_operators_reader() + .expect("Failed to get operators reader"); + + let mut call_functions = HashSet::new(); + while let Ok(op) = instructions_reader.read() { + if let Operator::Call { function_index } = op { + let function_usize: usize = function_index.try_into().unwrap(); + call_functions.insert(function_usize); + } + } + + self.call_graph + .insert(self.call_graph.len(), call_functions); + } + + pub fn process_imports( + &mut self, + import_section: ImportSectionReader, + import_extraction_enabled: bool, + ) { + for (index, import) in import_section.into_iter().flatten().enumerate() { + if import_extraction_enabled { + self.imports.push(import.name.to_string()); + } + self.call_graph.insert(index, HashSet::new()); + if WRITE_OP.contains(&import.name) { + self.write_index_functions.insert(index); + } + } + + self.imports.sort(); + } + + pub fn detect_write_operations_in_views(&mut self) { + let mut visited: HashSet = HashSet::new(); + for index in self.view_endpoints.values() { + mark_write( + *index, + &self.call_graph, + &mut self.write_index_functions, + &mut visited, + ); + } + + for (name, index) in &self.view_endpoints { + if self.write_index_functions.contains(index) { + println!( + "{} {}", + "Write storage operation in VIEW endpoint:" + .to_string() + .red() + .bold(), + name.red().bold() + ); + } + } + } + + fn parse_export_section( + &mut self, + export_section: ExportSectionReader, + view_endpoints: &[&str], + ) { + for export in export_section { + let export = export.expect("Failed to read export section"); + if let wasmparser::ExternalKind::Func = export.kind { + if view_endpoints.contains(&export.name) { + self.view_endpoints + .insert(export.name.to_owned(), export.index.try_into().unwrap()); + } + } + } } } pub(crate) fn populate_wasm_info( path: String, wasm_data: Vec, - extract_imports_enabled: bool, + import_extraction_enabled: bool, check_ei: &Option, + view_endpoints: Vec<&str>, ) -> Result { - let mut imports = Vec::new(); - let mut allocator_trigger = false; - let mut ei_check = false; - let mut memory_grow_flag = false; - let mut has_panic: PanicReport = PanicReport::default(); + let mut wasm_info = WasmInfo::default(); let parser = Parser::new(0); for payload in parser.parse_all(&wasm_data) { match payload? { Payload::ImportSection(import_section) => { - imports = extract_imports(import_section, extract_imports_enabled); - ei_check |= is_ei_valid(imports.clone(), check_ei); + wasm_info.process_imports(import_section, import_extraction_enabled); + wasm_info.ei_check |= is_ei_valid(&wasm_info.imports, check_ei); }, Payload::DataSection(data_section) => { - allocator_trigger |= is_fail_allocator_triggered(data_section.clone()); - has_panic.max_severity(data_section); + wasm_info.report.has_allocator |= is_fail_allocator_triggered(data_section.clone()); + wasm_info.report.has_panic.max_severity(data_section); }, Payload::CodeSectionEntry(code_section) => { - memory_grow_flag |= is_mem_grow(code_section); + wasm_info.memory_grow_flag |= is_mem_grow(&code_section); + wasm_info.create_call_graph(code_section); + }, + Payload::ExportSection(export_section) => { + wasm_info.parse_export_section(export_section, &view_endpoints); }, _ => (), } } + wasm_info.detect_write_operations_in_views(); + let report = ReportCreator { path, - has_allocator: allocator_trigger, - has_panic, + has_allocator: wasm_info.report.has_allocator, + has_panic: wasm_info.report.has_panic, }; Ok(WasmInfo { - imports, - ei_check, - memory_grow_flag, - has_format: true, + imports: wasm_info.imports, + ei_check: wasm_info.ei_check, + memory_grow_flag: wasm_info.memory_grow_flag, + call_graph: wasm_info.call_graph, report, + write_index_functions: wasm_info.write_index_functions, + view_endpoints: wasm_info.view_endpoints, }) } @@ -103,25 +204,34 @@ fn is_fail_allocator_triggered(data_section: DataSectionReader) -> bool { false } -pub fn extract_imports( - import_section: ImportSectionReader, - import_extraction_enabled: bool, -) -> Vec { - if !import_extraction_enabled { - return Vec::new(); - } - - let mut import_names = Vec::new(); - for import in import_section.into_iter().flatten() { - import_names.push(import.name.to_string()); +fn mark_write( + func: usize, + call_graph: &CallGraph, + write_functions: &mut HashSet, + visited: &mut HashSet, +) { + // Return early to prevent cycles. + if visited.contains(&func) { + return; } - import_names.sort(); + visited.insert(func); - import_names + if let Some(callees) = call_graph.get(&func) { + for &callee in callees { + if write_functions.contains(&callee) { + write_functions.insert(func); + } else { + mark_write(callee, call_graph, write_functions, visited); + if write_functions.contains(&callee) { + write_functions.insert(func); + } + } + } + } } -fn is_ei_valid(imports: Vec, check_ei: &Option) -> bool { +fn is_ei_valid(imports: &[String], check_ei: &Option) -> bool { if let Some(ei) = check_ei { let mut num_errors = 0; for import in imports { @@ -138,7 +248,7 @@ fn is_ei_valid(imports: Vec, check_ei: &Option) -> bool { false } -fn is_mem_grow(code_section: FunctionBody) -> bool { +fn is_mem_grow(code_section: &FunctionBody) -> bool { let mut instructions_reader = code_section .get_operators_reader() .expect("Failed to get operators reader"); diff --git a/framework/meta-lib/src/tools/wasm_extractor_test.rs b/framework/meta-lib/src/tools/wasm_extractor_test.rs index 030cddc503..346d0d855e 100644 --- a/framework/meta-lib/src/tools/wasm_extractor_test.rs +++ b/framework/meta-lib/src/tools/wasm_extractor_test.rs @@ -1,9 +1,161 @@ #[cfg(test)] pub mod tests { + use std::collections::{HashMap, HashSet}; + use wat::Parser; use crate::tools::{panic_report::PanicReport, wasm_extractor::populate_wasm_info}; + const ADDER_WITH_ERR_IN_VIEW: &str = r#" +(module $adder_wasm.wasm + (type (;0;) (func (param i32 i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (type (;3;) (func (param i32 i32 i32) (result i32))) + (type (;4;) (func)) + (type (;5;) (func (param i32))) + (type (;6;) (func (param i32 i32 i32))) + (type (;7;) (func (param i32) (result i32))) + (import "env" "bigIntGetUnsignedArgument" (func $bigIntGetUnsignedArgument (;0;) (type 0))) + (import "env" "getNumArguments" (func $getNumArguments (;1;) (type 1))) + (import "env" "signalError" (func $signalError (;2;) (type 0))) + (import "env" "mBufferFromBigIntUnsigned" (func $mBufferFromBigIntUnsigned (;3;) (type 2))) + (import "env" "mBufferStorageStore" (func $mBufferStorageStore (;4;) (type 2))) + (import "env" "mBufferStorageLoad" (func $mBufferStorageLoad (;5;) (type 2))) + (import "env" "mBufferToBigIntUnsigned" (func $mBufferToBigIntUnsigned (;6;) (type 2))) + (import "env" "mBufferSetBytes" (func $mBufferSetBytes (;7;) (type 3))) + (import "env" "checkNoPayment" (func $checkNoPayment (;8;) (type 4))) + (import "env" "bigIntFinishUnsigned" (func $bigIntFinishUnsigned (;9;) (type 5))) + (import "env" "bigIntAdd" (func $bigIntAdd (;10;) (type 6))) + (func $_ZN13multiversx_sc2io16arg_nested_tuple15load_single_arg17hcaef680f5560198bE (;11;) (type 1) (result i32) + (local i32) + i32.const 0 + call $_ZN26multiversx_sc_wasm_adapter3api13managed_types19static_var_api_node11next_handle17h315bf8f89178ffe1E + local.tee 0 + call $bigIntGetUnsignedArgument + local.get 0 + ) + (func $_ZN26multiversx_sc_wasm_adapter3api13managed_types19static_var_api_node11next_handle17h315bf8f89178ffe1E (;12;) (type 1) (result i32) + (local i32) + i32.const 0 + i32.const 0 + i32.load offset=131100 + i32.const -1 + i32.add + local.tee 0 + i32.store offset=131100 + local.get 0 + ) + (func $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17h8cbbe81aa680cf46E (;13;) (type 5) (param i32) + block ;; label = @1 + call $getNumArguments + local.get 0 + i32.ne + br_if 0 (;@1;) + return + end + i32.const 131072 + i32.const 25 + call $signalError + unreachable + ) + (func $_ZN13multiversx_sc7storage7mappers19single_value_mapper31SingleValueMapper$LT$SA$C$T$GT$3set17h00fe05a7d5154b39E (;14;) (type 0) (param i32 i32) + (local i32) + call $_ZN26multiversx_sc_wasm_adapter3api13managed_types19static_var_api_node11next_handle17h315bf8f89178ffe1E + local.tee 2 + local.get 1 + call $mBufferFromBigIntUnsigned + drop + local.get 0 + local.get 2 + call $mBufferStorageStore + drop + ) + (func $_ZN13multiversx_sc7storage7mappers19single_value_mapper35SingleValueMapper$LT$SA$C$T$C$A$GT$3get17hb9977d4c8d45f0d8E (;15;) (type 7) (param i32) (result i32) + (local i32) + local.get 0 + call $_ZN26multiversx_sc_wasm_adapter3api13managed_types19static_var_api_node11next_handle17h315bf8f89178ffe1E + local.tee 1 + call $mBufferStorageLoad + drop + local.get 1 + call $_ZN26multiversx_sc_wasm_adapter3api13managed_types19static_var_api_node11next_handle17h315bf8f89178ffe1E + local.tee 0 + call $mBufferToBigIntUnsigned + drop + local.get 0 + ) + (func $_ZN34_$LT$C$u20$as$u20$adder..Adder$GT$3sum17h7cc7cc0602a1f97fE (;16;) (type 1) (result i32) + (local i32) + call $_ZN26multiversx_sc_wasm_adapter3api13managed_types19static_var_api_node11next_handle17h315bf8f89178ffe1E + local.tee 0 + i32.const 131097 + i32.const 3 + call $mBufferSetBytes + drop + local.get 0 + ) + (func $init (;17;) (type 4) + (local i32) + call $checkNoPayment + i32.const 1 + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17h8cbbe81aa680cf46E + call $_ZN13multiversx_sc2io16arg_nested_tuple15load_single_arg17hcaef680f5560198bE + local.set 0 + call $_ZN34_$LT$C$u20$as$u20$adder..Adder$GT$3sum17h7cc7cc0602a1f97fE + local.get 0 + call $_ZN13multiversx_sc7storage7mappers19single_value_mapper31SingleValueMapper$LT$SA$C$T$GT$3set17h00fe05a7d5154b39E + ) + (func $getSum (;18;) (type 4) + call $checkNoPayment + i32.const 0 + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17h8cbbe81aa680cf46E + call $_ZN34_$LT$C$u20$as$u20$adder..Adder$GT$3sum17h7cc7cc0602a1f97fE + call $_ZN13multiversx_sc7storage7mappers19single_value_mapper35SingleValueMapper$LT$SA$C$T$C$A$GT$3get17hb9977d4c8d45f0d8E + call $bigIntFinishUnsigned + ) + (func $add (;19;) (type 4) + (local i32 i32 i32) + call $checkNoPayment + i32.const 1 + call $_ZN13multiversx_sc2io16arg_nested_tuple22check_num_arguments_eq17h8cbbe81aa680cf46E + call $_ZN13multiversx_sc2io16arg_nested_tuple15load_single_arg17hcaef680f5560198bE + local.set 0 + call $_ZN34_$LT$C$u20$as$u20$adder..Adder$GT$3sum17h7cc7cc0602a1f97fE + local.tee 1 + call $_ZN13multiversx_sc7storage7mappers19single_value_mapper35SingleValueMapper$LT$SA$C$T$C$A$GT$3get17hb9977d4c8d45f0d8E + local.tee 2 + local.get 2 + local.get 0 + call $bigIntAdd + local.get 1 + local.get 2 + call $_ZN13multiversx_sc7storage7mappers19single_value_mapper31SingleValueMapper$LT$SA$C$T$GT$3set17h00fe05a7d5154b39E + ) + (func $callBack (;20;) (type 4)) + (table (;0;) 1 1 funcref) + (memory (;0;) 3) + (global $__stack_pointer (;0;) (mut i32) i32.const 131072) + (global (;1;) i32 i32.const 131104) + (global (;2;) i32 i32.const 131104) + (export "memory" (memory 0)) + (export "init" (func $init)) + (export "getSum" (func $getSum)) + (export "add" (func $add)) + (export "callBack" (func $callBack)) + (export "upgrade" (func $init)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (data $.rodata (;0;) (i32.const 131072) "wrong number of argumentssum") + (data $.data (;1;) (i32.const 131100) "8\ff\ff\ff") + (@producers + (language "Rust" "") + (processed-by "rustc" "1.80.1 (3f5fd8dd4 2024-08-06)") + ) + (@custom "target_features" (after data) "\02+\0fmutable-globals+\08sign-ext") +) +"#; + const EMPTY_WITH_FAIL_ALLOCATOR: &str = r#" (module $empty_wasm.wasm (type (;0;) (func (result i32))) @@ -256,8 +408,9 @@ pub mod tests { #[test] fn test_empty() { if let Ok(content) = Parser::new().parse_bytes(None, EMPTY_DBG_WAT.as_bytes()) { - let wasm_info = populate_wasm_info(String::new(), content.to_vec(), false, &None) - .expect("Unable to parse WASM content."); + let wasm_info = + populate_wasm_info(String::new(), content.to_vec(), false, &None, Vec::new()) + .expect("Unable to parse WASM content."); assert!(!wasm_info.memory_grow_flag); assert!(!wasm_info.report.has_allocator); assert_eq!( @@ -270,8 +423,9 @@ pub mod tests { #[test] fn test_empty_with_mem_grow() { if let Ok(content) = Parser::new().parse_bytes(None, EMPTY_WITH_MEM_GROW.as_bytes()) { - let wasm_info = populate_wasm_info(String::new(), content.to_vec(), false, &None) - .expect("Unable to parse WASM content."); + let wasm_info = + populate_wasm_info(String::new(), content.to_vec(), false, &None, Vec::new()) + .expect("Unable to parse WASM content."); assert!(wasm_info.memory_grow_flag); assert!(!wasm_info.report.has_allocator); assert_eq!( @@ -284,8 +438,9 @@ pub mod tests { #[test] fn test_empty_with_fail_allocator() { if let Ok(content) = Parser::new().parse_bytes(None, EMPTY_WITH_FAIL_ALLOCATOR.as_bytes()) { - let wasm_info = populate_wasm_info(String::new(), content.to_vec(), false, &None) - .expect("Unable to parse WASM content."); + let wasm_info = + populate_wasm_info(String::new(), content.to_vec(), false, &None, Vec::new()) + .expect("Unable to parse WASM content."); assert!(!wasm_info.memory_grow_flag); assert!(wasm_info.report.has_allocator); assert_eq!( @@ -294,4 +449,54 @@ pub mod tests { ); } } + + #[test] + fn test_adder_with_write_op_in_view() { + let view_endpoints: Vec<&str> = Vec::from(["getSum", "add"]); + + let expected_view_index: HashMap = + HashMap::from([("getSum".to_string(), 18), ("add".to_string(), 19)]); + let expected_write_index_functions: HashSet = HashSet::from([4, 19, 14]); + let expected_call_graph: HashMap> = HashMap::from([ + (0, HashSet::new()), + (1, HashSet::new()), + (2, HashSet::new()), + (3, HashSet::new()), + (4, HashSet::new()), + (5, HashSet::new()), + (6, HashSet::new()), + (7, HashSet::new()), + (8, HashSet::new()), + (9, HashSet::new()), + (10, HashSet::new()), + (11, HashSet::from([12, 0])), + (12, HashSet::new()), + (13, HashSet::from([1, 2])), + (14, HashSet::from([12, 3, 4])), + (15, HashSet::from([12, 5, 6])), + (16, HashSet::from([12, 7])), + (17, HashSet::from([8, 13, 11, 16, 14])), + (18, HashSet::from([8, 13, 16, 15, 9])), + (19, HashSet::from([8, 13, 11, 16, 15, 10, 14])), + (20, HashSet::new()), + ]); + + if let Ok(content) = Parser::new().parse_bytes(None, ADDER_WITH_ERR_IN_VIEW.as_bytes()) { + let wasm_info = populate_wasm_info( + String::new(), + content.to_vec(), + false, + &None, + view_endpoints, + ) + .expect("Unable to parse WASM content."); + + assert_eq!( + expected_write_index_functions, + wasm_info.write_index_functions + ); + assert_eq!(expected_call_graph, wasm_info.call_graph); + assert_eq!(expected_view_index, wasm_info.view_endpoints); + } + } } diff --git a/framework/meta/src/cmd/info.rs b/framework/meta/src/cmd/info.rs index 2d36399474..6d51ccd39f 100644 --- a/framework/meta/src/cmd/info.rs +++ b/framework/meta/src/cmd/info.rs @@ -1,10 +1,11 @@ -use super::upgrade::print_tree_dir_metadata; use crate::{ cli::InfoArgs, folder_structure::{dir_pretty_print, RelevantDirectories}, version_history::LAST_UPGRADE_VERSION, }; +use super::print_util::print_tree_dir_metadata; + pub fn call_info(args: &InfoArgs) { let path = if let Some(some_path) = &args.path { some_path.as_str() diff --git a/framework/meta/src/cmd/print_util.rs b/framework/meta/src/cmd/print_util.rs index 7b3e0be9b8..e93099f348 100644 --- a/framework/meta/src/cmd/print_util.rs +++ b/framework/meta/src/cmd/print_util.rs @@ -1,6 +1,9 @@ use colored::Colorize; +use multiversx_sc_meta_lib::version::FrameworkVersion; use std::path::Path; +use crate::folder_structure::{DependencyReference, DirectoryType, RelevantDirectory}; + pub fn print_all_count(num_contract_crates: usize) { println!( "\n{}", @@ -24,3 +27,33 @@ pub fn print_all_command(meta_path: &Path, cargo_run_args: &[String]) { cargo_run_args.join(" "), ); } + +pub fn print_tree_dir_metadata(dir: &RelevantDirectory, last_version: &FrameworkVersion) { + match dir.dir_type { + DirectoryType::Contract => print!(" {}", "[contract]".blue()), + DirectoryType::Lib => print!(" {}", "[lib]".magenta()), + } + + match &dir.version { + DependencyReference::Version(version_req) => { + let version_string = format!("[{}]", version_req.semver); + if version_req.semver == *last_version { + print!(" {}", version_string.green()); + } else { + print!(" {}", version_string.red()); + }; + }, + DependencyReference::Git(git_reference) => { + let git_string = format!( + "[git: {} rev: {}]", + git_reference.git.truecolor(255, 127, 0), + git_reference.rev.truecolor(255, 127, 0) + ); + print!(" {}", git_string.truecolor(255, 198, 0)); + }, + DependencyReference::Path(path_buf) => { + let git_string = format!("[path: {}]", path_buf.truecolor(255, 127, 0)); + print!(" {}", git_string.truecolor(255, 198, 0)); + }, + } +} diff --git a/framework/meta/src/cmd/upgrade.rs b/framework/meta/src/cmd/upgrade.rs index ab14b75075..b4b9a28904 100644 --- a/framework/meta/src/cmd/upgrade.rs +++ b/framework/meta/src/cmd/upgrade.rs @@ -8,5 +8,4 @@ mod upgrade_print; mod upgrade_selector; mod upgrade_settings; -pub use upgrade_print::print_tree_dir_metadata; pub use upgrade_selector::upgrade_sc; diff --git a/framework/meta/src/cmd/upgrade/upgrade_print.rs b/framework/meta/src/cmd/upgrade/upgrade_print.rs index 51d66f9333..6c6360c14a 100644 --- a/framework/meta/src/cmd/upgrade/upgrade_print.rs +++ b/framework/meta/src/cmd/upgrade/upgrade_print.rs @@ -1,8 +1,5 @@ use crate::{ - folder_structure::{ - DirectoryType::{Contract, Lib}, - RelevantDirectory, - }, + folder_structure::{DependencyReference, RelevantDirectory}, version::FrameworkVersion, }; use colored::Colorize; @@ -82,20 +79,6 @@ pub fn print_postprocessing_after_39_1(path: &Path) { ); } -pub fn print_tree_dir_metadata(dir: &RelevantDirectory, last_version: &FrameworkVersion) { - match dir.dir_type { - Contract => print!(" {}", "[contract]".blue()), - Lib => print!(" {}", "[lib]".magenta()), - } - - let version_string = format!("[{}]", dir.version.semver); - if dir.version.semver == *last_version { - print!(" {}", version_string.green()); - } else { - print!(" {}", version_string.red()); - }; -} - pub fn print_cargo_dep_remove(path: &Path, dep_name: &str) { println!( "{}/dependencies/{}", @@ -113,15 +96,17 @@ pub fn print_cargo_dep_add(path: &Path, dep_name: &str) { } pub fn print_cargo_check(dir: &RelevantDirectory) { - println!( - "\n{}", - format!( - "Running cargo check after upgrading to version {} in {}\n", - dir.version.semver, - dir.path.display(), - ) - .purple() - ); + if let DependencyReference::Version(version_req) = &dir.version { + println!( + "\n{}", + format!( + "Running cargo check after upgrading to version {} in {}\n", + version_req.semver, + dir.path.display(), + ) + .purple() + ); + } } pub fn print_cargo_check_fail() { diff --git a/framework/meta/src/cmd/upgrade/upgrade_selector.rs b/framework/meta/src/cmd/upgrade/upgrade_selector.rs index 0f958b176e..5031339220 100644 --- a/framework/meta/src/cmd/upgrade/upgrade_selector.rs +++ b/framework/meta/src/cmd/upgrade/upgrade_selector.rs @@ -1,6 +1,6 @@ use crate::{ cli::UpgradeArgs, - cmd::upgrade::upgrade_settings::UpgradeSettings, + cmd::{print_util::print_tree_dir_metadata, upgrade::upgrade_settings::UpgradeSettings}, folder_structure::{dir_pretty_print, RelevantDirectories, RelevantDirectory}, version::FrameworkVersion, version_history::{versions_iter, CHECK_AFTER_UPGRADE_TO, LAST_UPGRADE_VERSION, VERSIONS}, diff --git a/framework/meta/src/folder_structure/relevant_directory.rs b/framework/meta/src/folder_structure/relevant_directory.rs index be0c4ce0ac..a42a7dd27a 100644 --- a/framework/meta/src/folder_structure/relevant_directory.rs +++ b/framework/meta/src/folder_structure/relevant_directory.rs @@ -6,7 +6,7 @@ use std::{ }; use toml::Value; -use super::version_req::VersionReq; +use super::{version_req::VersionReq, DependencyReference, GitReference}; /// Used for retrieving crate versions. pub const FRAMEWORK_CRATE_NAMES: &[&str] = &[ @@ -35,7 +35,7 @@ pub enum DirectoryType { #[derive(Debug, Clone)] pub struct RelevantDirectory { pub path: PathBuf, - pub version: VersionReq, + pub version: DependencyReference, pub upgrade_in_progress: Option<(FrameworkVersion, FrameworkVersion)>, pub dir_type: DirectoryType, } @@ -89,7 +89,7 @@ impl RelevantDirectories { pub fn count_for_version(&self, version: &FrameworkVersion) -> usize { self.0 .iter() - .filter(|dir| dir.version.semver == *version) + .filter(|dir| dir.version.is_framework_version(version)) .count() } @@ -99,13 +99,13 @@ impl RelevantDirectories { ) -> impl Iterator { self.0 .iter() - .filter(move |dir| dir.version.semver == *version) + .filter(move |dir| dir.version.is_framework_version(version)) } /// Marks all appropriate directories as ready for upgrade. pub fn start_upgrade(&mut self, from_version: FrameworkVersion, to_version: FrameworkVersion) { for dir in self.0.iter_mut() { - if dir.version.semver == from_version { + if dir.version.is_framework_version(&from_version) { dir.upgrade_in_progress = Some((from_version.clone(), to_version.clone())); } } @@ -116,7 +116,9 @@ impl RelevantDirectories { pub fn finish_upgrade(&mut self) { for dir in self.0.iter_mut() { if let Some((_, to_version)) = &dir.upgrade_in_progress { - dir.version.semver = to_version.clone(); + if let DependencyReference::Version(version_req) = &mut dir.version { + version_req.semver = to_version.clone(); + } dir.upgrade_in_progress = None; } } @@ -136,7 +138,7 @@ fn populate_directories(path: &Path, ignore: &[String], result: &mut Vec bool { } } -fn find_framework_version_string(cargo_toml_contents: &CargoTomlContents) -> Option { +fn find_framework_toml_dependency( + cargo_toml_contents: &CargoTomlContents, +) -> Option { for &crate_name in FRAMEWORK_CRATE_NAMES { - if let Some(old_base) = cargo_toml_contents.dependency(crate_name) { - if let Some(Value::String(s)) = old_base.get("version") { - return Some(s.clone()); + if let Some(dep_value) = cargo_toml_contents.dependency(crate_name) { + if let Some(Value::String(s)) = dep_value.get("path") { + return Some(DependencyReference::Path(s.clone())); + } + + if let Some(Value::String(git)) = dep_value.get("git") { + let rev = dep_value + .get("rev") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + return Some(DependencyReference::Git(GitReference { + git: git.clone(), + rev: rev.to_owned(), + })); + } + + if let Some(Value::String(s)) = dep_value.get("version") { + return Some(DependencyReference::Version(VersionReq::from_string( + s.clone(), + ))); } } } @@ -200,10 +221,10 @@ impl RelevantDirectory { } } -fn find_framework_version(dir_path: &Path) -> Option { +fn find_framework_dependency(dir_path: &Path) -> Option { if let Some(cargo_toml_contents) = load_cargo_toml_contents(dir_path) { - if let Some(version) = find_framework_version_string(&cargo_toml_contents) { - return Some(VersionReq::from_string(version)); + if let Some(dep_ref) = find_framework_toml_dependency(&cargo_toml_contents) { + return Some(dep_ref); } } diff --git a/framework/meta/src/folder_structure/version_req.rs b/framework/meta/src/folder_structure/version_req.rs index bbfa752a12..8d7863920d 100644 --- a/framework/meta/src/folder_structure/version_req.rs +++ b/framework/meta/src/folder_structure/version_req.rs @@ -3,6 +3,24 @@ use crate::{ version_history::{find_version_by_str, LAST_VERSION}, }; +/// Models how a dependency is expressed in Cargo.toml. +#[derive(Debug, Clone)] +pub enum DependencyReference { + Version(VersionReq), + Git(GitReference), + Path(String), +} + +impl DependencyReference { + pub fn is_framework_version(&self, version: &FrameworkVersion) -> bool { + if let DependencyReference::Version(version_req) = self { + &version_req.semver == version + } else { + false + } + } +} + /// Crate version requirements, as expressed in Cargo.toml. A very crude version. /// /// TODO: replace with semver::VersionReq at some point. @@ -36,3 +54,12 @@ impl VersionReq { } } } + +/// A dependency reference to a git commit. We mostly use git commits when referencing git. +/// +/// TODO: add support for `branch` and `tag`. +#[derive(Debug, Clone)] +pub struct GitReference { + pub git: String, + pub rev: String, +} diff --git a/framework/scenario/src/api/impl_vh/debug_api.rs b/framework/scenario/src/api/impl_vh/debug_api.rs index b493047686..43bed2fe5d 100644 --- a/framework/scenario/src/api/impl_vh/debug_api.rs +++ b/framework/scenario/src/api/impl_vh/debug_api.rs @@ -5,7 +5,7 @@ use multiversx_chain_vm::{ tx_mock::{TxContext, TxContextRef, TxContextStack, TxPanic}, vm_hooks::{DebugApiVMHooksHandler, VMHooksDispatcher}, }; -use multiversx_sc::err_msg; +use multiversx_sc::{chain_core::types::ReturnCode, err_msg}; use crate::debug_executor::{StaticVarData, StaticVarStack}; @@ -61,7 +61,7 @@ impl VMHooksApiBackend for DebugApiBackend { fn assert_live_handle(handle: &Self::HandleType) { if !handle.is_on_current_context() { debugger_panic( - err_msg::DEBUG_API_ERR_STATUS, + ReturnCode::DebugApiError, err_msg::DEBUG_API_ERR_HANDLE_STALE, ); } @@ -93,7 +93,7 @@ impl std::fmt::Debug for DebugApi { } } -fn debugger_panic(status: u64, message: &str) { +fn debugger_panic(status: ReturnCode, message: &str) { TxContextRef::new_from_static().replace_tx_result_with_error(TxPanic::new(status, message)); std::panic::panic_any(BreakpointValue::SignalError); } @@ -101,7 +101,7 @@ fn debugger_panic(status: u64, message: &str) { fn assert_handles_on_same_context(handle1: &DebugHandle, handle2: &DebugHandle) { if !handle1.is_on_same_context(handle2) { debugger_panic( - err_msg::DEBUG_API_ERR_STATUS, + ReturnCode::DebugApiError, err_msg::DEBUG_API_ERR_HANDLE_CONTEXT_MISMATCH, ); } diff --git a/framework/scenario/src/debug_executor/contract_container.rs b/framework/scenario/src/debug_executor/contract_container.rs index 8d89062d15..188bd6bdd5 100644 --- a/framework/scenario/src/debug_executor/contract_container.rs +++ b/framework/scenario/src/debug_executor/contract_container.rs @@ -1,6 +1,6 @@ use multiversx_chain_vm::tx_mock::{TxContextRef, TxFunctionName, TxPanic}; use multiversx_chain_vm_executor::{BreakpointValue, ExecutorError, Instance, MemLength, MemPtr}; -use multiversx_sc::contract_base::CallableContract; +use multiversx_sc::{chain_core::types::ReturnCode, contract_base::CallableContract}; use std::sync::Arc; use super::{catch_tx_panic, StaticVarStack}; @@ -81,7 +81,10 @@ impl Instance for ContractContainerRef { if call_successful { Ok(()) } else { - Err(TxPanic::new(1, "invalid function (not found)")) + Err(TxPanic::new( + ReturnCode::FunctionNotFound, + "invalid function (not found)", + )) } }); diff --git a/framework/scenario/src/facade/result_handlers.rs b/framework/scenario/src/facade/result_handlers.rs index 88e90d0cd5..454f235b22 100644 --- a/framework/scenario/src/facade/result_handlers.rs +++ b/framework/scenario/src/facade/result_handlers.rs @@ -2,6 +2,7 @@ mod expect_error; mod expect_message; mod expect_status; mod expect_value; +mod returns_handled_or_err; mod returns_logs; mod returns_message; mod returns_new_bech32_address; @@ -14,6 +15,7 @@ pub use expect_error::ExpectError; pub use expect_message::ExpectMessage; pub use expect_status::ExpectStatus; pub use expect_value::ExpectValue; +pub use returns_handled_or_err::ReturnsHandledOrError; pub use returns_logs::ReturnsLogs; pub use returns_message::ReturnsMessage; pub use returns_new_bech32_address::ReturnsNewBech32Address; diff --git a/framework/scenario/src/facade/result_handlers/returns_handled_or_err.rs b/framework/scenario/src/facade/result_handlers/returns_handled_or_err.rs new file mode 100644 index 0000000000..12dede24ff --- /dev/null +++ b/framework/scenario/src/facade/result_handlers/returns_handled_or_err.rs @@ -0,0 +1,96 @@ +use std::marker::PhantomData; + +use multiversx_sc::{ + tuple_util::NestedTupleFlatten, + types::{ + OriginalResultMarker, RHList, RHListAppendRet, RHListExec, RHListItem, RHListItemExec, + TxEnv, + }, +}; + +use crate::scenario_model::{CheckValue, TxExpect, TxResponse, TxResponseStatus}; + +/// Indicates that a `Result` will be returned, either with the handled result, +/// according to the nested result handlers, or with an error in case of a failed transaction. +pub struct ReturnsHandledOrError +where + Env: TxEnv, + NHList: RHList, +{ + _phantom_env: PhantomData, + _phantom_original: PhantomData, + pub nested_handlers: NHList, +} + +impl Default for ReturnsHandledOrError> +where + Env: TxEnv, +{ + fn default() -> Self { + ReturnsHandledOrError { + _phantom_env: PhantomData, + _phantom_original: PhantomData, + nested_handlers: OriginalResultMarker::new(), + } + } +} + +impl ReturnsHandledOrError> +where + Env: TxEnv, +{ + pub fn new() -> Self { + ReturnsHandledOrError::default() + } +} + +impl ReturnsHandledOrError +where + Env: TxEnv, + NHList: RHListExec, +{ + pub fn returns(self, item: RH) -> ReturnsHandledOrError + where + RH: RHListItem, + NHList: RHListAppendRet, + { + ReturnsHandledOrError { + _phantom_env: PhantomData, + _phantom_original: PhantomData, + nested_handlers: self.nested_handlers.append_ret(item), + } + } +} + +impl RHListItem + for ReturnsHandledOrError +where + Env: TxEnv, + NHList: RHListExec, + NHList::ListReturns: NestedTupleFlatten, +{ + type Returns = Result<::Unpacked, TxResponseStatus>; +} + +impl RHListItemExec + for ReturnsHandledOrError +where + Env: TxEnv, + NHList: RHListExec, + NHList::ListReturns: NestedTupleFlatten, +{ + fn item_tx_expect(&self, mut prev: TxExpect) -> TxExpect { + prev.status = CheckValue::Star; + prev.message = CheckValue::Star; + prev + } + + fn item_process_result(self, raw_result: &TxResponse) -> Self::Returns { + if raw_result.tx_error.is_success() { + let tuple_result = self.nested_handlers.list_process_result(raw_result); + Ok(tuple_result.flatten_unpack()) + } else { + Err(raw_result.tx_error.clone()) + } + } +} diff --git a/framework/scenario/src/facade/result_handlers/returns_status.rs b/framework/scenario/src/facade/result_handlers/returns_status.rs index 184163619d..30eba0730c 100644 --- a/framework/scenario/src/facade/result_handlers/returns_status.rs +++ b/framework/scenario/src/facade/result_handlers/returns_status.rs @@ -31,6 +31,6 @@ where } fn item_process_result(self, raw_result: &TxResponse) -> Self::Returns { - raw_result.tx_error.status + raw_result.tx_error.status.as_u64() } } diff --git a/framework/scenario/src/imports.rs b/framework/scenario/src/imports.rs index 171862dc9a..b82583b97c 100644 --- a/framework/scenario/src/imports.rs +++ b/framework/scenario/src/imports.rs @@ -24,3 +24,5 @@ pub use crate::{ whitebox_legacy::*, ScenarioTxRun, }; + +pub use crate::multiversx_sc::chain_core::types::ReturnCode; diff --git a/framework/scenario/src/scenario/model/transaction/tx_error.rs b/framework/scenario/src/scenario/model/transaction/tx_error.rs deleted file mode 100644 index d0a2b5a3e4..0000000000 --- a/framework/scenario/src/scenario/model/transaction/tx_error.rs +++ /dev/null @@ -1,21 +0,0 @@ -#[derive(Debug, Default, Clone)] -pub struct TxResponseStatus { - pub status: u64, - pub message: String, -} - -impl TxResponseStatus { - pub fn is_success(&self) -> bool { - self.status == 0 - } -} - -impl std::fmt::Display for TxResponseStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.is_success() { - write!(f, "transaction successful") - } else { - write!(f, "transaction error: {}", self.message) - } - } -} diff --git a/framework/scenario/src/scenario/model/transaction/tx_expect.rs b/framework/scenario/src/scenario/model/transaction/tx_expect.rs index fe8f0f5eaa..875dc2a280 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_expect.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_expect.rs @@ -8,8 +8,7 @@ use crate::{ scenario_model::Checkable, }; use multiversx_chain_vm::tx_mock::result_values_to_string; - -const USER_ERROR_CODE: u64 = 4; +use multiversx_sc::chain_core::types::ReturnCode; #[derive(Debug, Clone)] pub struct TxExpect { @@ -67,7 +66,7 @@ impl TxExpect { where BytesValue: From, { - Self::err(USER_ERROR_CODE, err_msg_expr) + Self::err(ReturnCode::UserError, err_msg_expr) } pub fn no_result(mut self) -> Self { @@ -100,7 +99,7 @@ impl TxExpect { fn check_response(&self, tx_response: &TxResponse) { assert!( - self.status.check(tx_response.tx_error.status), + self.status.check(tx_response.tx_error.status.as_u64()), "{}result code mismatch. Want: {}. Have: {}. Message: {}", &self.additional_error_message, self.status, diff --git a/framework/scenario/src/scenario/model/transaction/tx_response_status.rs b/framework/scenario/src/scenario/model/transaction/tx_response_status.rs index e7902047c6..17860ab05f 100644 --- a/framework/scenario/src/scenario/model/transaction/tx_response_status.rs +++ b/framework/scenario/src/scenario/model/transaction/tx_response_status.rs @@ -1,15 +1,17 @@ -#[derive(Debug, Default, Clone)] +use multiversx_sc::chain_core::types::ReturnCode; + +#[derive(Debug, Default, Clone, PartialEq, Eq)] /// The status of a transaction. pub struct TxResponseStatus { /// The status of the transaction. - pub status: u64, + pub status: ReturnCode, /// The message of the transaction. pub message: String, } impl TxResponseStatus { /// Creates a [`TxResponseStatus`] - pub fn new(status: u64, message: &str) -> Self { + pub fn new(status: ReturnCode, message: &str) -> Self { Self { status, message: message.to_string(), @@ -18,12 +20,12 @@ impl TxResponseStatus { /// Creates a [`TxResponseStatus`] that signals an error. pub fn signal_error(message: &str) -> Self { - Self::new(4, message) + Self::new(ReturnCode::UserError, message) } /// Checks if the transaction was successful. pub fn is_success(&self) -> bool { - self.status == 0 + self.status.is_success() } } diff --git a/framework/scenario/src/scenario/model/value/value_set_u64.rs b/framework/scenario/src/scenario/model/value/value_set_u64.rs index b332543116..a0305eb9f5 100644 --- a/framework/scenario/src/scenario/model/value/value_set_u64.rs +++ b/framework/scenario/src/scenario/model/value/value_set_u64.rs @@ -4,6 +4,7 @@ use crate::scenario_format::{ value_interpreter::{interpret_string, interpret_subtree}, }; +use multiversx_sc::chain_core::types::ReturnCode; use num_bigint::BigUint; use num_traits::ToPrimitive; use std::fmt; @@ -108,6 +109,12 @@ impl From for U64Value { } } +impl From for U64Value { + fn from(from: ReturnCode) -> Self { + U64Value::from(from.as_u64()) + } +} + impl From<&str> for U64Value { fn from(from: &str) -> Self { U64Value::interpret_from(from, &InterpreterContext::default()) diff --git a/framework/scenario/src/scenario/run_vm/tx_output_check.rs b/framework/scenario/src/scenario/run_vm/tx_output_check.rs index eeec7a1c4e..7a3380de46 100644 --- a/framework/scenario/src/scenario/run_vm/tx_output_check.rs +++ b/framework/scenario/src/scenario/run_vm/tx_output_check.rs @@ -8,7 +8,7 @@ use multiversx_chain_vm::{ pub fn check_tx_output(tx_id: &str, tx_expect: &TxExpect, tx_result: &TxResult) { let have_str = tx_result.result_message.as_str(); assert!( - tx_expect.status.check(tx_result.result_status), + tx_expect.status.check(tx_result.result_status.as_u64()), "result code mismatch. Tx id: '{}'. Want: {}. Have: {}. Message: {}", tx_id, tx_expect.status, diff --git a/framework/scenario/tests/contract_without_macros.rs b/framework/scenario/tests/contract_without_macros.rs index a08f1a4bbd..6d445d3fc4 100644 --- a/framework/scenario/tests/contract_without_macros.rs +++ b/framework/scenario/tests/contract_without_macros.rs @@ -387,16 +387,10 @@ mod sample_adder { false, ); let mut endpoint_abi = multiversx_sc::abi::EndpointAbi::new( - &[], "getSum", "sum", - false, - false, multiversx_sc::abi::EndpointMutabilityAbi::Readonly, multiversx_sc::abi::EndpointTypeAbi::Endpoint, - &[], - &[], - false, ); endpoint_abi .add_output::< @@ -408,47 +402,30 @@ mod sample_adder { >(); contract_abi.endpoints.push(endpoint_abi); let mut endpoint_abi = multiversx_sc::abi::EndpointAbi::new( - &[], "init", "init", - false, - false, multiversx_sc::abi::EndpointMutabilityAbi::Mutable, multiversx_sc::abi::EndpointTypeAbi::Init, - &[], - &[], - false, ); endpoint_abi.add_input::>("initial_value"); contract_abi.add_type_descriptions::>(); contract_abi.constructors.push(endpoint_abi); let mut endpoint_abi = multiversx_sc::abi::EndpointAbi::new( - &[], "upgrade", "upgrade", - false, - false, multiversx_sc::abi::EndpointMutabilityAbi::Mutable, multiversx_sc::abi::EndpointTypeAbi::Upgrade, - &[], - &[], - false, ); endpoint_abi.add_input::>("initial_value"); contract_abi.add_type_descriptions::>(); contract_abi.upgrade_constructors.push(endpoint_abi); let mut endpoint_abi = multiversx_sc::abi::EndpointAbi::new( - &["Add desired amount to the storage variable."], "add", "add", - false, - false, multiversx_sc::abi::EndpointMutabilityAbi::Mutable, multiversx_sc::abi::EndpointTypeAbi::Endpoint, - &[], - &[], - false, - ); + ) + .with_docs("Add desired amount to the storage variable."); endpoint_abi.add_input::>("value"); contract_abi.add_type_descriptions::>(); contract_abi.endpoints.push(endpoint_abi); diff --git a/framework/snippets-base/src/interactor_tx.rs b/framework/snippets-base/src/interactor_tx.rs index 0521300977..c017699468 100644 --- a/framework/snippets-base/src/interactor_tx.rs +++ b/framework/snippets-base/src/interactor_tx.rs @@ -13,6 +13,6 @@ mod interactor_query_step; pub use interactor_exec_env::InteractorEnvExec; pub use interactor_exec_step::InteractorExecStep; -pub use interactor_prepare_async::InteractorPrepareAsync; +pub use interactor_prepare_async::{InteractorPrepareAsync, InteractorRunAsync}; pub use interactor_query_env::InteractorEnvQuery; pub use interactor_query_step::InteractorQueryStep; diff --git a/framework/snippets-base/src/interactor_tx/interactor_exec_call.rs b/framework/snippets-base/src/interactor_tx/interactor_exec_call.rs index eadd122ed2..000daba91a 100644 --- a/framework/snippets-base/src/interactor_tx/interactor_exec_call.rs +++ b/framework/snippets-base/src/interactor_tx/interactor_exec_call.rs @@ -15,7 +15,50 @@ use multiversx_sdk::gateway::GatewayAsyncService; use crate::InteractorBase; -use super::{InteractorEnvExec, InteractorExecStep, InteractorPrepareAsync}; +use super::{InteractorEnvExec, InteractorExecStep, InteractorPrepareAsync, InteractorRunAsync}; + +async fn run_async_call<'w, GatewayProxy, From, To, Payment, Gas, RH>( + tx: Tx< + InteractorEnvExec<'w, GatewayProxy>, + From, + To, + Payment, + Gas, + FunctionCall, + RH, + >, +) -> ::Unpacked +where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + To: TxToSpecified>, + Payment: TxPayment>, + Gas: TxGas>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + let mut step_wrapper = tx.tx_to_step(); + step_wrapper.env.world.sc_call(&mut step_wrapper.step).await; + step_wrapper.process_result() +} + +impl<'w, GatewayProxy, From, To, Payment, Gas, RH> InteractorRunAsync + for Tx, From, To, Payment, Gas, FunctionCall, RH> +where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + To: TxToSpecified>, + Payment: TxPayment>, + Gas: TxGas>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + type Result = ::Unpacked; + + fn run(self) -> impl std::future::Future { + run_async_call(self) + } +} impl<'w, GatewayProxy, From, To, Payment, Gas, RH> InteractorPrepareAsync for Tx, From, To, Payment, Gas, FunctionCall, RH> diff --git a/framework/snippets-base/src/interactor_tx/interactor_exec_deploy.rs b/framework/snippets-base/src/interactor_tx/interactor_exec_deploy.rs index 142325e73a..8871a90030 100644 --- a/framework/snippets-base/src/interactor_tx/interactor_exec_deploy.rs +++ b/framework/snippets-base/src/interactor_tx/interactor_exec_deploy.rs @@ -14,7 +14,66 @@ use multiversx_sdk::gateway::GatewayAsyncService; use crate::InteractorBase; -use super::{InteractorEnvExec, InteractorExecStep, InteractorPrepareAsync}; +use super::{ + interactor_prepare_async::InteractorRunAsync, InteractorEnvExec, InteractorExecStep, + InteractorPrepareAsync, +}; + +#[allow(clippy::type_complexity)] +async fn run_async_deploy<'w, GatewayProxy, From, Payment, Gas, CodeValue, RH>( + tx: Tx< + InteractorEnvExec<'w, GatewayProxy>, + From, + (), + Payment, + Gas, + DeployCall, Code>, + RH, + >, +) -> ::Unpacked +where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + Payment: TxPayment>, + Gas: TxGas>, + CodeValue: TxCodeValue>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + let mut step_wrapper = tx.tx_to_step(); + step_wrapper + .env + .world + .sc_deploy(&mut step_wrapper.step) + .await; + step_wrapper.process_result() +} + +impl<'w, GatewayProxy, From, Payment, Gas, CodeValue, RH> InteractorRunAsync + for Tx< + InteractorEnvExec<'w, GatewayProxy>, + From, + (), + Payment, + Gas, + DeployCall, Code>, + RH, + > +where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + Payment: TxPayment>, + Gas: TxGas>, + CodeValue: TxCodeValue>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + type Result = ::Unpacked; + + fn run(self) -> impl std::future::Future { + run_async_deploy(self) + } +} impl<'w, GatewayProxy, From, Payment, Gas, CodeValue, RH> InteractorPrepareAsync for Tx< diff --git a/framework/snippets-base/src/interactor_tx/interactor_exec_transf.rs b/framework/snippets-base/src/interactor_tx/interactor_exec_transf.rs index bef6fb8032..bb7559ba93 100644 --- a/framework/snippets-base/src/interactor_tx/interactor_exec_transf.rs +++ b/framework/snippets-base/src/interactor_tx/interactor_exec_transf.rs @@ -5,7 +5,36 @@ use multiversx_sc_scenario::{ }; use multiversx_sdk::gateway::GatewayAsyncService; -use super::{InteractorEnvExec, InteractorExecStep, InteractorPrepareAsync}; +use super::{InteractorEnvExec, InteractorExecStep, InteractorPrepareAsync, InteractorRunAsync}; + +async fn run_async_transfer<'w, GatewayProxy, From, To, Payment, Gas>( + tx: Tx, From, To, Payment, Gas, (), ()>, +) where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + To: TxToSpecified>, + Payment: TxPayment>, + Gas: TxGas>, +{ + let step_wrapper = tx.tx_to_step(); + step_wrapper.env.world.transfer(step_wrapper.step).await; +} + +impl<'w, GatewayProxy, From, To, Payment, Gas> InteractorRunAsync + for Tx, From, To, Payment, Gas, (), ()> +where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + To: TxToSpecified>, + Payment: TxPayment>, + Gas: TxGas>, +{ + type Result = (); + + fn run(self) -> impl std::future::Future { + run_async_transfer(self) + } +} impl<'w, GatewayProxy, From, To, Payment, Gas> InteractorPrepareAsync for Tx, From, To, Payment, Gas, (), ()> diff --git a/framework/snippets-base/src/interactor_tx/interactor_exec_upgrade.rs b/framework/snippets-base/src/interactor_tx/interactor_exec_upgrade.rs index a99b5e3131..ab04e94846 100644 --- a/framework/snippets-base/src/interactor_tx/interactor_exec_upgrade.rs +++ b/framework/snippets-base/src/interactor_tx/interactor_exec_upgrade.rs @@ -15,7 +15,59 @@ use multiversx_sdk::gateway::GatewayAsyncService; use crate::InteractorBase; -use super::{InteractorEnvExec, InteractorExecStep, InteractorPrepareAsync}; +use super::{InteractorEnvExec, InteractorExecStep, InteractorPrepareAsync, InteractorRunAsync}; + +#[allow(clippy::type_complexity)] +async fn run_async_upgrade<'w, GatewayProxy, From, To, Gas, CodeValue, RH>( + tx: Tx< + InteractorEnvExec<'w, GatewayProxy>, + From, + To, + NotPayable, + Gas, + UpgradeCall, Code>, + RH, + >, +) -> ::Unpacked +where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + To: TxToSpecified>, + Gas: TxGas>, + CodeValue: TxCodeValue>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + let mut step_wrapper = tx.tx_to_step(); + step_wrapper.env.world.sc_call(&mut step_wrapper.step).await; + step_wrapper.process_result() +} + +impl<'w, GatewayProxy, From, To, Gas, CodeValue, RH> InteractorRunAsync + for Tx< + InteractorEnvExec<'w, GatewayProxy>, + From, + To, + NotPayable, + Gas, + UpgradeCall, Code>, + RH, + > +where + GatewayProxy: GatewayAsyncService, + From: TxFromSpecified>, + To: TxToSpecified>, + Gas: TxGas>, + CodeValue: TxCodeValue>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + type Result = ::Unpacked; + + fn run(self) -> impl std::future::Future { + run_async_upgrade(self) + } +} impl<'w, GatewayProxy, From, To, Gas, CodeValue, RH> InteractorPrepareAsync for Tx< diff --git a/framework/snippets-base/src/interactor_tx/interactor_prepare_async.rs b/framework/snippets-base/src/interactor_tx/interactor_prepare_async.rs index 62131b78a2..fcb8f7a7d9 100644 --- a/framework/snippets-base/src/interactor_tx/interactor_prepare_async.rs +++ b/framework/snippets-base/src/interactor_tx/interactor_prepare_async.rs @@ -18,5 +18,15 @@ where pub trait InteractorPrepareAsync { type Exec; + #[deprecated( + since = "0.54.0", + note = "Calling `.prepare_async()` no longer necessary, `.run()` can be called directly." + )] fn prepare_async(self) -> Self::Exec; } + +pub trait InteractorRunAsync { + type Result; + + fn run(self) -> impl std::future::Future; +} diff --git a/framework/snippets-base/src/interactor_tx/interactor_query_call.rs b/framework/snippets-base/src/interactor_tx/interactor_query_call.rs index 6491a1e34d..70821b831b 100644 --- a/framework/snippets-base/src/interactor_tx/interactor_query_call.rs +++ b/framework/snippets-base/src/interactor_tx/interactor_query_call.rs @@ -12,7 +12,42 @@ use multiversx_sdk::gateway::GatewayAsyncService; use crate::InteractorBase; -use super::{InteractorEnvQuery, InteractorPrepareAsync, InteractorQueryStep}; +use super::{InteractorEnvQuery, InteractorPrepareAsync, InteractorQueryStep, InteractorRunAsync}; + +async fn run_async_query<'w, GatewayProxy, To, Payment, RH>( + tx: Tx, (), To, Payment, (), FunctionCall, RH>, +) -> ::Unpacked +where + GatewayProxy: GatewayAsyncService, + To: TxToSpecified>, + Payment: TxNoPayment>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + let mut step_wrapper = tx.tx_to_query_step(); + step_wrapper + .env + .world + .sc_query(&mut step_wrapper.step) + .await; + step_wrapper.process_result() +} + +impl<'w, GatewayProxy, To, Payment, RH> InteractorRunAsync + for Tx, (), To, Payment, (), FunctionCall, RH> +where + GatewayProxy: GatewayAsyncService, + To: TxToSpecified>, + Payment: TxNoPayment>, + RH: RHListExec>, + RH::ListReturns: NestedTupleFlatten, +{ + type Result = ::Unpacked; + + fn run(self) -> impl std::future::Future { + run_async_query(self) + } +} impl<'w, GatewayProxy, To, Payment, RH> InteractorPrepareAsync for Tx, (), To, Payment, (), FunctionCall, RH> diff --git a/framework/snippets-dapp/src/imports.rs b/framework/snippets-dapp/src/imports.rs index 2556622174..0f8f13ba35 100644 --- a/framework/snippets-dapp/src/imports.rs +++ b/framework/snippets-dapp/src/imports.rs @@ -1,9 +1,11 @@ pub use crate::multiversx_sc_scenario::imports::*; pub use multiversx_sc_snippets_base::{ - dns_address_for_name, InteractorBase, InteractorPrepareAsync, StepBuffer, + dns_address_for_name, InteractorBase, InteractorPrepareAsync, InteractorRunAsync, StepBuffer, }; pub use multiversx_sdk_dapp::{core::test_wallets, data::keystore::InsertPassword, wallet::Wallet}; +pub use crate::DappInteractor; + pub use env_logger; diff --git a/framework/snippets/src/imports.rs b/framework/snippets/src/imports.rs index d92d73307c..fa9f8a7728 100644 --- a/framework/snippets/src/imports.rs +++ b/framework/snippets/src/imports.rs @@ -1,7 +1,7 @@ pub use multiversx_sc_snippets_base::multiversx_sc_scenario::imports::*; pub use multiversx_sc_snippets_base::{ - dns_address_for_name, InteractorBase, InteractorPrepareAsync, StepBuffer, + dns_address_for_name, InteractorBase, InteractorPrepareAsync, InteractorRunAsync, StepBuffer, }; pub use multiversx_sc_snippets_base::sdk::{ diff --git a/sdk/http/examples/generate_mnemonic.rs b/sdk/http/examples/generate_mnemonic.rs deleted file mode 100644 index 2dcb1b6ed9..0000000000 --- a/sdk/http/examples/generate_mnemonic.rs +++ /dev/null @@ -1,9 +0,0 @@ -use multiversx_sdk::wallet::Wallet; - -fn main() { - let mnemonic = Wallet::generate_mnemonic(); - println!("mnemonic: {mnemonic}"); - - let private_key = Wallet::get_private_key_from_mnemonic(mnemonic, 0, 0); - println!("private key: {:?}", private_key.to_string()); -} diff --git a/tools/interactor-system-func-calls/src/system_sc_interact.rs b/tools/interactor-system-func-calls/src/system_sc_interact.rs index 97d72beb01..10830eb072 100644 --- a/tools/interactor-system-func-calls/src/system_sc_interact.rs +++ b/tools/interactor-system-func-calls/src/system_sc_interact.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] // TODO: move prepare_async calls to a test for backwards compatibility and delete from here + mod system_sc_interact_cli; mod system_sc_interact_config; mod system_sc_interact_state; diff --git a/vm-core/src/types/flags.rs b/vm-core/src/types/flags.rs index cfe9fc28ef..6b2522c386 100644 --- a/vm-core/src/types/flags.rs +++ b/vm-core/src/types/flags.rs @@ -2,10 +2,12 @@ mod code_metadata; mod esdt_local_role; mod esdt_local_role_flags; mod esdt_token_type; +mod return_code; mod token_type; pub use code_metadata::CodeMetadata; pub use esdt_local_role::EsdtLocalRole; pub use esdt_local_role_flags::EsdtLocalRoleFlags; pub use esdt_token_type::EsdtTokenType; +pub use return_code::ReturnCode; pub use token_type::TokenType; diff --git a/vm-core/src/types/flags/return_code.rs b/vm-core/src/types/flags/return_code.rs new file mode 100644 index 0000000000..d1617f9aaf --- /dev/null +++ b/vm-core/src/types/flags/return_code.rs @@ -0,0 +1,91 @@ +const MESSAGE_OK: &str = "ok"; +const MESSAGE_FUNCTION_NOT_FOUND: &str = "function not found"; +const MESSAGE_WRONG_SIGNATURE: &str = "wrong signature for function"; +const MESSAGE_CONTRACT_NOT_FOUND: &str = "contract not found"; +const MESSAGE_USER_ERROR: &str = "user error"; +const MESSAGE_OUT_OF_GAS: &str = "out of gas"; +const MESSAGE_ACCOUNT_COLLISION: &str = "account collision"; +const MESSAGE_OUT_OF_FUNDS: &str = "out of funds"; +const MESSAGE_CALL_STACK_OVERFLOW: &str = "call stack overflow"; +const MESSAGE_CONTRACT_INVALID: &str = "contract invalid"; +const MESSAGE_EXECUTION_FAILED: &str = "execution failed"; +const MESSAGE_UNKNOWN_ERROR: &str = "unknown error"; + +#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)] +pub enum ReturnCode { + /// Returned when execution was completed normally. + #[default] + Success = 0, + + /// Returned when the input specifies a function name that does not exist or is not public. + FunctionNotFound = 1, + + /// Returned when the wrong number of arguments is provided. + FunctionWrongSignature = 2, + + /// Returned when the called contract does not exist. + ContractNotFound = 3, + + /// Returned for various execution errors. + UserError = 4, + + /// Returned when VM execution runs out of gas. + OutOfGas = 5, + + /// Returned when created account already exists. + AccountCollision = 6, + + /// Returned when the caller (sender) runs out of funds. + OutOfFunds = 7, + + /// Returned when stack overflow occurs. + CallStackOverFlow = 8, + + /// Returned when the contract is invalid. + ContractInvalid = 9, + + /// Returned when the execution of the specified function has failed. + ExecutionFailed = 10, + + /// Returned when the upgrade of the contract has failed + UpgradeFailed = 11, + + /// Returned when tx simulation fails execution + SimulateFailed = 12, + + /// Only occurs in the debugger context. + DebugApiError = 100, +} + +impl ReturnCode { + pub fn as_u64(self) -> u64 { + self as u64 + } + + pub fn is_success(self) -> bool { + self == ReturnCode::Success + } + + pub fn message(self) -> &'static str { + match self { + ReturnCode::Success => MESSAGE_OK, + ReturnCode::FunctionNotFound => MESSAGE_FUNCTION_NOT_FOUND, + ReturnCode::FunctionWrongSignature => MESSAGE_WRONG_SIGNATURE, + ReturnCode::ContractNotFound => MESSAGE_CONTRACT_NOT_FOUND, + ReturnCode::UserError => MESSAGE_USER_ERROR, + ReturnCode::OutOfGas => MESSAGE_OUT_OF_GAS, + ReturnCode::AccountCollision => MESSAGE_ACCOUNT_COLLISION, + ReturnCode::OutOfFunds => MESSAGE_OUT_OF_FUNDS, + ReturnCode::CallStackOverFlow => MESSAGE_CALL_STACK_OVERFLOW, + ReturnCode::ContractInvalid => MESSAGE_CONTRACT_INVALID, + ReturnCode::ExecutionFailed => MESSAGE_EXECUTION_FAILED, + _ => MESSAGE_UNKNOWN_ERROR, + } + } +} + +impl core::fmt::Display for ReturnCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.as_u64().fmt(f) + } +} diff --git a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_burn.rs b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_burn.rs index b3f42b3844..f87af9f766 100644 --- a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_burn.rs +++ b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_burn.rs @@ -1,3 +1,4 @@ +use multiversx_chain_core::types::ReturnCode; use num_bigint::BigUint; use crate::{ @@ -47,7 +48,7 @@ impl BuiltinFunction for ESDTLocalBurn { }; let tx_result = TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_logs: vec![esdt_nft_create_log], ..Default::default() }; diff --git a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_mint.rs b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_mint.rs index cb59eee3d5..b82c7ca8b4 100644 --- a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_mint.rs +++ b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_local_mint.rs @@ -1,3 +1,4 @@ +use multiversx_chain_core::types::ReturnCode; use num_bigint::BigUint; use crate::{ @@ -50,7 +51,7 @@ impl BuiltinFunction for ESDTLocalMint { }; let tx_result = TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_logs: vec![esdt_nft_create_log], ..Default::default() }; diff --git a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_quantity_mock.rs b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_quantity_mock.rs index 4ef229d686..c773ee80bc 100644 --- a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_quantity_mock.rs +++ b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_quantity_mock.rs @@ -1,3 +1,4 @@ +use multiversx_chain_core::types::ReturnCode; use num_bigint::BigUint; use crate::{ @@ -56,7 +57,7 @@ impl BuiltinFunction for ESDTNftAddQuantity { }; let tx_result = TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_logs: vec![esdt_nft_create_log], ..Default::default() }; diff --git a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_uri_mock.rs b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_uri_mock.rs index 3c4d2b986b..9685063fc9 100644 --- a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_uri_mock.rs +++ b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_add_uri_mock.rs @@ -1,3 +1,5 @@ +use multiversx_chain_core::types::ReturnCode; + use crate::{ chain_core::builtin_func_names::ESDT_NFT_ADD_URI_FUNC_NAME, tx_execution::BlockchainVMRef, @@ -53,7 +55,7 @@ impl BuiltinFunction for ESDTNftAddUri { }; let tx_result = TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_logs: vec![esdt_nft_create_log], ..Default::default() }; diff --git a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_burn_mock.rs b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_burn_mock.rs index ef6362f073..f92d89efbd 100644 --- a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_burn_mock.rs +++ b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_burn_mock.rs @@ -1,3 +1,4 @@ +use multiversx_chain_core::types::ReturnCode; use num_bigint::BigUint; use crate::{ @@ -53,7 +54,7 @@ impl BuiltinFunction for ESDTNftBurn { }; let tx_result = TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_logs: vec![esdt_nft_create_log], ..Default::default() }; diff --git a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_create_mock.rs b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_create_mock.rs index 0007ec3c91..5966330213 100644 --- a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_create_mock.rs +++ b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_create_mock.rs @@ -1,3 +1,4 @@ +use multiversx_chain_core::types::ReturnCode; use num_bigint::BigUint; use crate::{ @@ -79,7 +80,7 @@ impl BuiltinFunction for ESDTNftCreate { }; let tx_result = TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_values: vec![top_encode_u64(new_nonce)], result_logs: vec![esdt_nft_create_log], ..Default::default() diff --git a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_update_attriutes_mock.rs b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_update_attriutes_mock.rs index d6f088f4f6..42a6f965ae 100644 --- a/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_update_attriutes_mock.rs +++ b/vm/src/tx_execution/builtin_function_mocks/esdt_nft/esdt_nft_update_attriutes_mock.rs @@ -1,3 +1,5 @@ +use multiversx_chain_core::types::ReturnCode; + use crate::{ chain_core::builtin_func_names::ESDT_NFT_UPDATE_ATTRIBUTES_FUNC_NAME, tx_execution::BlockchainVMRef, @@ -52,7 +54,7 @@ impl BuiltinFunction for ESDTNftUpdateAttributes { }; let tx_result = TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_logs: vec![esdt_nft_create_log], ..Default::default() }; diff --git a/vm/src/tx_execution/exec_call.rs b/vm/src/tx_execution/exec_call.rs index ffc48e12ae..1b8e50b855 100644 --- a/vm/src/tx_execution/exec_call.rs +++ b/vm/src/tx_execution/exec_call.rs @@ -92,7 +92,7 @@ impl BlockchainVMRef { self.execute_builtin_function_or_default(tx_input, tx_cache, f) }); - if tx_result.result_status == 0 { + if tx_result.result_status.is_success() { blockchain_updates.apply(state); } @@ -155,7 +155,7 @@ impl BlockchainVMRef { // legacy async call // the async call also gets reset - if tx_result.result_status == 0 { + if tx_result.result_status.is_success() { if let Some(async_data) = pending_calls.async_call { let (async_result, callback_result) = self.execute_async_call_and_callback(async_data, state); diff --git a/vm/src/tx_mock/tx_async_call_data.rs b/vm/src/tx_mock/tx_async_call_data.rs index 45cbce639c..ab1b744a3b 100644 --- a/vm/src/tx_mock/tx_async_call_data.rs +++ b/vm/src/tx_mock/tx_async_call_data.rs @@ -56,8 +56,8 @@ pub fn async_callback_tx_input( async_result: &TxResult, builtin_functions: &BuiltinFunctionContainer, ) -> TxInput { - let mut args: Vec> = vec![result_status_bytes(async_result.result_status)]; - if async_result.result_status == 0 { + let mut args: Vec> = vec![result_status_bytes(async_result.result_status.as_u64())]; + if async_result.result_status.is_success() { args.extend_from_slice(async_result.result_values.as_slice()); } else { args.push(async_result.result_message.clone().into_bytes()); @@ -108,7 +108,7 @@ pub fn async_promise_callback_tx_input( async_result: &TxResult, builtin_functions: &BuiltinFunctionContainer, ) -> TxInput { - let callback_name = if async_result.result_status == 0 { + let callback_name = if async_result.result_status.is_success() { promise.success_callback.clone() } else { promise.error_callback.clone() @@ -122,7 +122,7 @@ pub fn async_promise_callback_tx_input( } pub fn merge_results(mut original: TxResult, mut new: TxResult) -> TxResult { - if original.result_status == 0 { + if original.result_status.is_success() { original.result_values.append(&mut new.result_values); original.result_logs.append(&mut new.result_logs); original.result_message = new.result_message; diff --git a/vm/src/tx_mock/tx_panic.rs b/vm/src/tx_mock/tx_panic.rs index 63d1154558..86875f765f 100644 --- a/vm/src/tx_mock/tx_panic.rs +++ b/vm/src/tx_mock/tx_panic.rs @@ -1,11 +1,13 @@ +use multiversx_chain_core::types::ReturnCode; + #[derive(Debug, Clone)] pub struct TxPanic { - pub status: u64, + pub status: ReturnCode, pub message: String, } impl TxPanic { - pub fn new(status: u64, message: &str) -> Self { + pub fn new(status: ReturnCode, message: &str) -> Self { Self { status, message: message.to_string(), @@ -13,10 +15,10 @@ impl TxPanic { } pub fn user_error(message: &str) -> TxPanic { - TxPanic::new(4, message) + TxPanic::new(ReturnCode::UserError, message) } pub fn vm_error(message: &str) -> TxPanic { - TxPanic::new(10, message) + TxPanic::new(ReturnCode::ExecutionFailed, message) } } diff --git a/vm/src/tx_mock/tx_result.rs b/vm/src/tx_mock/tx_result.rs index 4234f3a207..68a5319948 100644 --- a/vm/src/tx_mock/tx_result.rs +++ b/vm/src/tx_mock/tx_result.rs @@ -1,11 +1,13 @@ use std::fmt; +use multiversx_chain_core::types::ReturnCode; + use super::{AsyncCallTxData, TxLog, TxPanic, TxResultCalls}; #[derive(Clone, Debug)] #[must_use] pub struct TxResult { - pub result_status: u64, + pub result_status: ReturnCode, pub result_message: String, pub result_values: Vec>, pub result_logs: Vec, @@ -24,7 +26,7 @@ pub struct TxResult { impl Default for TxResult { fn default() -> Self { TxResult { - result_status: 0, + result_status: ReturnCode::Success, result_message: String::new(), result_values: Vec::new(), result_logs: Vec::new(), @@ -55,7 +57,7 @@ impl TxResult { pub fn from_panic_string(s: &str) -> Self { TxResult { - result_status: 4, + result_status: ReturnCode::UserError, result_message: s.to_string(), result_values: Vec::new(), result_logs: Vec::new(), @@ -72,7 +74,7 @@ impl TxResult { S: Into, { TxResult { - result_status: 10, + result_status: ReturnCode::ExecutionFailed, result_message: result_message.into(), ..Default::default() } @@ -94,7 +96,7 @@ impl TxResult { pub fn assert_ok(&self) { assert!( - self.result_status == 0, + self.result_status.is_success(), "Tx success expected, but failed. Status: {}, message: \"{}\"", self.result_status, self.result_message.as_str() @@ -111,7 +113,7 @@ impl TxResult { self.result_message.as_str() ); assert!( - self.result_status == expected_status, + self.result_status.as_u64() == expected_status, "Tx error status mismatch. Want status {}, message \"{}\". Have status {}, message \"{}\"", expected_status, expected_message, @@ -121,7 +123,7 @@ impl TxResult { } pub fn assert_user_error(&self, expected_message: &str) { - self.assert_error(4, expected_message); + self.assert_error(ReturnCode::UserError.as_u64(), expected_message); } } diff --git a/vm/src/vm_hooks/vh_handler/vh_error.rs b/vm/src/vm_hooks/vh_handler/vh_error.rs index ac1b4b4448..9b72740c94 100644 --- a/vm/src/vm_hooks/vh_handler/vh_error.rs +++ b/vm/src/vm_hooks/vh_handler/vh_error.rs @@ -1,3 +1,5 @@ +use multiversx_chain_core::types::ReturnCode; + use crate::{types::RawHandle, vm_hooks::VMHooksHandlerSource}; use super::VMHooksManagedTypes; @@ -8,7 +10,7 @@ pub trait VMHooksError: VMHooksHandlerSource { // run `clear & cargo test -- --nocapture` to see the output println!("{}", std::str::from_utf8(message).unwrap()); - self.halt_with_error(4, std::str::from_utf8(message).unwrap()) + self.halt_with_error(ReturnCode::UserError, std::str::from_utf8(message).unwrap()) } } 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 618788d966..20ba8d20b3 100644 --- a/vm/src/vm_hooks/vh_impl/vh_debug_api.rs +++ b/vm/src/vm_hooks/vh_impl/vh_debug_api.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, MutexGuard}; +use multiversx_chain_core::types::ReturnCode; use multiversx_chain_vm_executor::BreakpointValue; use crate::{ @@ -36,10 +37,10 @@ impl VMHooksHandlerSource for DebugApiVMHooksHandler { self.0.m_types_lock() } - fn halt_with_error(&self, status: u64, message: &str) -> ! { + fn halt_with_error(&self, status: ReturnCode, message: &str) -> ! { *self.0.result_lock() = TxResult::from_panic_obj(&TxPanic::new(status, message)); let breakpoint = match status { - 4 => BreakpointValue::SignalError, + ReturnCode::UserError => BreakpointValue::SignalError, _ => BreakpointValue::ExecutionFailed, }; std::panic::panic_any(breakpoint); @@ -127,7 +128,7 @@ impl VMHooksHandlerSource for DebugApiVMHooksHandler { execute_current_tx_context_input, ); - if tx_result.result_status == 0 { + if tx_result.result_status.is_success() { self.sync_call_post_processing(tx_result, blockchain_updates) } else { // also kill current execution @@ -168,11 +169,11 @@ impl VMHooksHandlerSource for DebugApiVMHooksHandler { ); match tx_result.result_status { - 0 => ( + ReturnCode::Success => ( new_address, self.sync_call_post_processing(tx_result, blockchain_updates), ), - 10 => self.vm_error(&tx_result.result_message), // TODO: not sure it's the right condition, it catches insufficient funds + ReturnCode::ExecutionFailed => self.vm_error(&tx_result.result_message), // TODO: not sure it's the right condition, it catches insufficient funds _ => self.vm_error(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT), } } @@ -198,12 +199,12 @@ impl VMHooksHandlerSource for DebugApiVMHooksHandler { ); match tx_result.result_status { - 0 => { + ReturnCode::Success => { self.0.result_lock().all_calls.push(async_call_data); let _ = self.sync_call_post_processing(tx_result, blockchain_updates); }, - 10 => self.vm_error(&tx_result.result_message), // TODO: not sure it's the right condition, it catches insufficient funds + ReturnCode::ExecutionFailed => self.vm_error(&tx_result.result_message), // TODO: not sure it's the right condition, it catches insufficient funds _ => self.vm_error(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT), } } diff --git a/vm/src/vm_hooks/vh_impl/vh_single_tx_api.rs b/vm/src/vm_hooks/vh_impl/vh_single_tx_api.rs index 27fa484a4a..d29eb20ef4 100644 --- a/vm/src/vm_hooks/vh_impl/vh_single_tx_api.rs +++ b/vm/src/vm_hooks/vh_impl/vh_single_tx_api.rs @@ -3,6 +3,8 @@ use std::{ sync::{Arc, Mutex, MutexGuard}, }; +use multiversx_chain_core::types::ReturnCode; + use crate::{ tx_mock::{BackTransfers, TxFunctionName, TxInput, TxManagedTypes, TxResult}, types::{VMAddress, VMCodeMetadata}, @@ -57,7 +59,7 @@ impl VMHooksHandlerSource for SingleTxApiVMHooksHandler { self.0.managed_types.lock().unwrap() } - fn halt_with_error(&self, status: u64, message: &str) -> ! { + fn halt_with_error(&self, status: ReturnCode, message: &str) -> ! { panic!("VM error occured, status: {status}, message: {message}") } diff --git a/vm/src/vm_hooks/vh_impl/vh_static_api.rs b/vm/src/vm_hooks/vh_impl/vh_static_api.rs index aba07908e0..9f9a445b37 100644 --- a/vm/src/vm_hooks/vh_impl/vh_static_api.rs +++ b/vm/src/vm_hooks/vh_impl/vh_static_api.rs @@ -1,5 +1,7 @@ use std::sync::{Mutex, MutexGuard}; +use multiversx_chain_core::types::ReturnCode; + use crate::{ tx_mock::{BackTransfers, TxFunctionName, TxInput, TxLog, TxManagedTypes, TxResult}, types::{VMAddress, VMCodeMetadata}, @@ -28,7 +30,7 @@ impl VMHooksHandlerSource for StaticApiVMHooksHandler { self.0.lock().unwrap() } - fn halt_with_error(&self, status: u64, message: &str) -> ! { + fn halt_with_error(&self, status: ReturnCode, message: &str) -> ! { panic!("VM error occured, status: {status}, message: {message}") } diff --git a/vm/src/vm_hooks/vh_source.rs b/vm/src/vm_hooks/vh_source.rs index 0ad2e59e2a..7370c09e87 100644 --- a/vm/src/vm_hooks/vh_source.rs +++ b/vm/src/vm_hooks/vh_source.rs @@ -1,5 +1,7 @@ use std::{fmt::Debug, sync::MutexGuard}; +use multiversx_chain_core::types::ReturnCode; + use crate::{ tx_mock::{BackTransfers, TxFunctionName, TxInput, TxLog, TxManagedTypes, TxResult}, types::{VMAddress, VMCodeMetadata, H256}, @@ -10,10 +12,10 @@ use crate::{ pub trait VMHooksHandlerSource: Debug { fn m_types_lock(&self) -> MutexGuard; - fn halt_with_error(&self, status: u64, message: &str) -> !; + fn halt_with_error(&self, status: ReturnCode, message: &str) -> !; fn vm_error(&self, message: &str) -> ! { - self.halt_with_error(10, message) + self.halt_with_error(ReturnCode::ExecutionFailed, message) } fn input_ref(&self) -> &TxInput;