Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion chain/vm/src/host/context/tx_context_ref.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{ops::Deref, sync::Arc};
use std::{
ops::Deref,
sync::{Arc, Weak},
};

use crate::host::context::{TxContext, TxResult};

Expand Down Expand Up @@ -62,7 +65,30 @@ impl TxContextRef {
Arc::ptr_eq(&this.0, &other.0)
}

/// Returns a raw pointer to the underlying `TxContext`.
///
/// This is useful for pointer comparisons, particularly when comparing
/// with weak references to determine if they point to the same context.
pub fn as_ptr(&self) -> *const TxContext {
Arc::as_ptr(&self.0)
}

pub fn into_ref(self) -> Arc<TxContext> {
self.0
}

/// Creates a new [`Weak`] pointer to the [`TxContext`].
///
/// This is the preferred way to obtain a non‑owning reference to the underlying
/// `TxContext` when you need to store a handle that should not keep the transaction
/// alive on its own. In particular, this method underpins the weak‑pointer pattern
/// used by [`DebugHandle`], which holds a `Weak<TxContext>` so that debug tooling
/// can observe a transaction while it exists, without extending its lifetime.
///
/// Callers that use the returned [`Weak`] must call [`Weak::upgrade`] before
/// accessing the `TxContext` and be prepared to handle the case where upgrading
/// fails because the transaction context has already been dropped.
pub fn downgrade(&self) -> Weak<TxContext> {
Arc::downgrade(&self.0)
}
}
3 changes: 1 addition & 2 deletions framework/scenario/src/api/impl_vh/debug_api.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use multiversx_chain_vm::{
executor::{VMHooks, VMHooksEarlyExit},
host::context::TxContextRef,
host::vm_hooks::{TxVMHooksContext, VMHooksDispatcher},
};
use multiversx_sc::{chain_core::types::ReturnCode, err_msg};
Expand Down Expand Up @@ -32,7 +31,7 @@ impl VMHooksApiBackend for DebugApiBackend {
where
F: FnOnce(&mut dyn VMHooks) -> Result<R, VMHooksEarlyExit>,
{
let tx_context_ref = TxContextRef(handle.context.clone());
let tx_context_ref = handle.to_tx_context_ref();
let vh_context = TxVMHooksContext::new(tx_context_ref, ContractDebugInstanceState);
let mut dispatcher = VMHooksDispatcher::new(vh_context);
f(&mut dispatcher).unwrap_or_else(|err| ContractDebugInstanceState::early_exit_panic(err))
Expand Down
58 changes: 44 additions & 14 deletions framework/scenario/src/api/impl_vh/debug_handle_vh.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::sync::Arc;
use std::sync::Weak;

use multiversx_chain_vm::host::context::TxContext;
use multiversx_chain_vm::host::context::{TxContext, TxContextRef};
use multiversx_sc::{
api::{HandleConstraints, RawHandle},
codec::TryStaticCast,
Expand All @@ -10,22 +10,30 @@ use crate::executor::debug::ContractDebugStack;

#[derive(Clone)]
pub struct DebugHandle {
/// TODO: would be nice to be an actual TxContextRef,
/// but that requires changing the debugger scripts
pub(crate) context: Arc<TxContext>,
/// Only keep a weak reference to the context, to avoid stray handles keeping the context from being released.
/// Using the pointer after the context is released will panic.
pub(crate) context: Weak<TxContext>,
raw_handle: RawHandle,
}

impl DebugHandle {
/// Should almost never call directly, only used directly in a test.
pub fn new_with_explicit_context_ref(context: Weak<TxContext>, raw_handle: RawHandle) -> Self {
Self {
context,
raw_handle,
}
}

pub fn is_on_current_context(&self) -> bool {
Arc::ptr_eq(
&self.context,
&ContractDebugStack::static_peek().tx_context_ref.into_ref(),
std::ptr::eq(
self.context.as_ptr(),
ContractDebugStack::static_peek().tx_context_ref.as_ptr(),
)
}

pub fn is_on_same_context(&self, other: &DebugHandle) -> bool {
Arc::ptr_eq(&self.context, &other.context)
Weak::ptr_eq(&self.context, &other.context)
}

pub fn assert_current_context(&self) {
Expand All @@ -34,6 +42,30 @@ impl DebugHandle {
"Managed value not used in original context"
);
}

/// Upgrades the weak reference to a strong `TxContextRef`.
///
/// This method attempts to upgrade the weak reference stored in this handle
/// to a strong reference. This is necessary when you need to access the
/// underlying `TxContext` for operations.
///
/// # Panics
///
/// Panics if the `TxContext` is no longer valid (has been dropped). This can
/// happen if the object was created on a VM execution stack frame that has
/// already been popped, or if objects are mixed between different execution
/// contexts during whitebox testing.
pub fn to_tx_context_ref(&self) -> TxContextRef {
let tx_context_arc = self.context.upgrade().unwrap_or_else(|| {
panic!(
"TxContext is no longer valid for handle {}.
The object was created on a VM execution stack frame that has already been popped.
This can sometimes happen during whitebox testing if the objects are mixed between execution contexts.",
self.raw_handle
)
});
TxContextRef::new(tx_context_arc)
}
}

impl core::fmt::Debug for DebugHandle {
Expand All @@ -44,10 +76,8 @@ impl core::fmt::Debug for DebugHandle {

impl HandleConstraints for DebugHandle {
fn new(handle: multiversx_sc::api::RawHandle) -> Self {
Self {
context: ContractDebugStack::static_peek().tx_context_ref.into_ref(),
raw_handle: handle,
}
let context = ContractDebugStack::static_peek().tx_context_ref.downgrade();
DebugHandle::new_with_explicit_context_ref(context, handle)
}

fn to_be_bytes(&self) -> [u8; 4] {
Expand All @@ -73,7 +103,7 @@ impl PartialEq<RawHandle> for DebugHandle {

impl PartialEq<DebugHandle> for DebugHandle {
fn eq(&self, other: &DebugHandle) -> bool {
Arc::ptr_eq(&self.context, &other.context) && self.raw_handle == other.raw_handle
Weak::ptr_eq(&self.context, &other.context) && self.raw_handle == other.raw_handle
}
}

Expand Down
44 changes: 39 additions & 5 deletions tools/rust-debugger/format-tests/src/format_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use multiversx_sc_scenario::imports::*;
use multiversx_sc_scenario::{
executor::debug::{ContractDebugInstance, ContractDebugStack},
imports::*,
};

macro_rules! push {
($list: ident, $name:ident, $expected: expr ) => {{
Expand All @@ -13,7 +16,8 @@ macro_rules! push {
// they have to be cloned if used before that point
#[allow(clippy::redundant_clone)]
fn main() {
DebugApi::dummy();
// Set up a dummy context on the debug stack, required for all managed types
ContractDebugStack::static_push(ContractDebugInstance::dummy());

// Used by the python script which checks the variable summaries
let mut to_check: Vec<(String, String)> = Vec::new();
Expand Down Expand Up @@ -76,7 +80,11 @@ fn main() {
let hex_esdt_safe_address = Address::new(hex_esdt_safe);
let esdt_safe_managed_address: ManagedAddress<DebugApi> =
ManagedAddress::from(hex_esdt_safe_address);
push!(to_check, esdt_safe_managed_address, "\"esdt-safe_____________\" - (32) 0x00000000000000000500657364742d736166655f5f5f5f5f5f5f5f5f5f5f5f5f");
push!(
to_check,
esdt_safe_managed_address,
"\"esdt-safe_____________\" - (32) 0x00000000000000000500657364742d736166655f5f5f5f5f5f5f5f5f5f5f5f5f"
);

let test_token_identifier: TestTokenIdentifier = TestTokenIdentifier::new("TEST-123456");
push!(to_check, test_token_identifier, "\"str:TEST-123456\"");
Expand Down Expand Up @@ -137,7 +145,11 @@ fn main() {
100,
5000u64.into(),
));
push!(to_check, managed_vec_of_payments, "(2) { [0] = { token_identifier: \"MYTOK-123456\", nonce: 42, amount: 1000 }, [1] = { token_identifier: \"MYTOK-abcdef\", nonce: 100, amount: 5000 } }");
push!(
to_check,
managed_vec_of_payments,
"(2) { [0] = { token_identifier: \"MYTOK-123456\", nonce: 42, amount: 1000 }, [1] = { token_identifier: \"MYTOK-abcdef\", nonce: 100, amount: 5000 } }"
);

let egld_or_esdt_token_identifier_egld: EgldOrEsdtTokenIdentifier<DebugApi> =
EgldOrEsdtTokenIdentifier::egld();
Expand Down Expand Up @@ -169,7 +181,11 @@ fn main() {
DebugApi,
ManagedVec<DebugApi, ManagedAddress<DebugApi>>,
> = ManagedOption::some(managed_vec_of_addresses.clone());
push!(to_check, managed_option_of_vec_of_addresses, "ManagedOption::some((1) { [0] = (32) 0x000000000000000000010000000000000000000000000000000000000002ffff })");
push!(
to_check,
managed_option_of_vec_of_addresses,
"ManagedOption::some((1) { [0] = (32) 0x000000000000000000010000000000000000000000000000000000000002ffff })"
);

// 5. SC wasm - heap
let heap_address: Address = managed_address.to_address();
Expand Down Expand Up @@ -244,7 +260,25 @@ fn main() {
"OptionalValue::Some(<invalid handle: raw_handle -1000 not found in big_int_map>)"
);

// Invalid TxContext test - simulate access after context change
// This test relies on the debugger pretty printer's error handling
// to detect when a weak pointer becomes invalid
let biguint_with_invalid_context =
unsafe { BigUint::<DebugApi>::from_handle(create_handle_from_dropped_context()) };
push!(
to_check,
biguint_with_invalid_context,
"<invalid weak pointer: TxContext has been dropped for handle -100>"
);

breakpoint_marker_end_of_main();

// Clean up the dummy entry on stack
ContractDebugStack::static_pop();
}

fn create_handle_from_dropped_context() -> DebugHandle {
DebugHandle::new_with_explicit_context_ref(std::sync::Weak::new(), -100i32)
}

fn breakpoint_marker_end_of_main() {}
41 changes: 41 additions & 0 deletions tools/rust-debugger/pretty-printers/format-python.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Python code formatting script for MultiversX SDK pretty-printers
echo "🐍 Formatting Python code..."

# Get the SDK root directory (3 levels up from this script)
SDK_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
PYTHON_CMD="$SDK_ROOT/.venv/bin/python"

# Find Python files in this directory
PYTHON_FILES=$(find . -name "*.py" -maxdepth 1)

if [ -z "$PYTHON_FILES" ]; then
echo "No Python files found to format"
exit 0
fi

echo "Found $(echo "$PYTHON_FILES" | wc -l) Python files to format"

# Check if virtual environment exists
if [ ! -f "$PYTHON_CMD" ]; then
echo "❌ Python virtual environment not found at $PYTHON_CMD"
echo "Please run 'configure_python_environment' from the SDK root first"
exit 1
fi

# 1. Sort imports with isort
echo "📦 Sorting imports with isort..."
$PYTHON_CMD -m isort $PYTHON_FILES

# 2. Format code with black
echo "🖤 Formatting code with black..."
$PYTHON_CMD -m black $PYTHON_FILES

# 3. Check style with flake8 (optional)
if [ "$1" = "--check" ]; then
echo "🔍 Checking style with flake8..."
$PYTHON_CMD -m flake8 $PYTHON_FILES --max-line-length=88 --extend-ignore=E203,W503
fi

echo "✅ Python formatting complete!"
Loading
Loading