Skip to main content

thruput_native_exec/
frame_builder.rs

1use crate::frame_result::FrameResult;
2use crate::mock_transaction_handler::{
3    MockInvocationFrame, MockInvocationFrameConfig,
4};
5use std::panic::UnwindSafe;
6
7/// What an `execute` closure tells the harness to do with its frame.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum FrameAction {
10    /// Leave the frame open for further `run_continuable` steps.
11    Continue,
12    /// Close the frame, producing its [`FrameResult`].
13    Close,
14}
15
16/// Builder for configuring and executing a single invocation frame.
17pub struct FrameBuilder<'a> {
18    config: MockInvocationFrameConfig<'a>,
19}
20
21impl<'a> FrameBuilder<'a> {
22    pub(crate) fn new(config: MockInvocationFrameConfig<'a>) -> Self {
23        Self { config }
24    }
25
26    /// Set the calldata the invoked frame observes (default: empty).
27    pub fn with_calldata(self, calldata: xdr_common::BytesM) -> Self {
28        Self {
29            config: self.config.with_calldata(calldata),
30        }
31    }
32
33    /// Set the frame's invoke flags, a bitset of
34    /// [`InvokeFlag`](thruput_xdr::invoke_flag::InvokeFlag) values
35    /// (default: none).
36    pub fn with_flags(
37        self,
38        flags: thruput_xdr::invoke_flag::InvokeFlagSet,
39    ) -> Self {
40        Self {
41            config: self.config.with_invoke_flags(flags),
42        }
43    }
44
45    /// Execute the frame with the given closure, then close it.
46    ///
47    /// The closure's return value is XDR-encoded into the frame's return
48    /// buffer (the same convention generated `#[contract]` entrypoints use),
49    /// and can be recovered from the result via `assert_return_as`.
50    /// Any value that encodes to zero bytes (e.g. `()`) skips the explicit
51    /// return and leaves the buffer untouched: an empty buffer is already
52    /// the valid encoding of such types, so decoding them still works.
53    /// Separately, closures that call `raw_Return` themselves never reach
54    /// this encode step at all, since `raw_Return` terminates the
55    /// invocation immediately (it never returns).
56    ///
57    /// Mixing an explicit `raw_Return` with a non-unit closure return is
58    /// unsupported; the explicit buffer wins and the closure's value is
59    /// never encoded.
60    ///
61    /// The return buffer is subject to the protocol's return-size cap
62    /// (1024 bytes); a larger encoded value deterministically reverts the
63    /// invocation, surfacing as an opaque frame failure. Keep test return
64    /// values small — return a digest or summary instead of bulk data.
65    ///
66    /// If you need to open child frames from within the closure, use
67    /// `run_continuable` instead, which provides a `FrameHandle` with
68    /// `child_call()`.
69    pub fn run<T: xdrs::WriteXDR>(
70        self,
71        f: impl FnOnce() -> T + UnwindSafe,
72    ) -> FrameResult {
73        let frame = self.config.build();
74        frame.execute(|_| {
75            let value = f();
76            let encoded = xdrs::cxs::encode(&value)
77                .expect("run(): failed to XDR-encode closure return value");
78            if encoded.is_empty() {
79                // Zero-byte encodings (e.g. `()`): an empty buffer is
80                // already the valid encoding, so close normally with no
81                // explicit return.
82                FrameAction::Close
83            } else {
84                // Sets the return buffer and diverges; the frame closes
85                // via the unwind path, equivalently to FrameAction::Close.
86                thruput_xdr::syscalls::raw_Return(&encoded, false, &[])
87            }
88        });
89        frame.result()
90    }
91
92    /// Execute the frame with a closure that receives a FrameHandle
93    /// and returns a FrameAction. If FrameAction::Continue, returns
94    /// a ContinuableFrame for further chaining.
95    ///
96    /// To return a typed value from a continuable frame, end the closing
97    /// step with an explicit `raw_Return` — it sets the buffer and
98    /// diverges (closing the frame), and `!` coerces to `FrameAction`:
99    ///
100    /// ```rust,ignore
101    /// let result = tx.call(&addr)
102    ///     .run_continuable(|frame| {
103    ///         // ... child calls, multi-step work ...
104    ///         raw_Return(&xdrs::cxs::encode(&value).unwrap(), false, &[])
105    ///     })
106    ///     .finish();
107    /// let v: T = result.assert_return_as();
108    /// ```
109    pub fn run_continuable(
110        self,
111        f: impl FnOnce(&mut FrameHandle) -> FrameAction + UnwindSafe,
112    ) -> ContinuableFrame<'a> {
113        let frame = self.config.build();
114        frame.execute(|inner_frame| {
115            let mut handle = FrameHandle { frame: inner_frame };
116            f(&mut handle)
117        });
118        ContinuableFrame { frame }
119    }
120}
121
122/// Handle to the current frame, available inside run_continuable closures.
123/// Provides child_call() for opening nested frames.
124pub struct FrameHandle<'a> {
125    frame: &'a mut MockInvocationFrame,
126}
127
128impl<'a> FrameHandle<'a> {
129    /// Open a child frame of the current frame, invoking `addr`. The
130    /// address must differ from every frame already open on the stack (the
131    /// reentrance rule); see [`MockInvocationFrame::open_child_frame`].
132    pub fn child_call(
133        &mut self,
134        addr: &thruput_xdr::types::ContractAddress,
135    ) -> FrameBuilder<'_> {
136        let config = self.frame.open_child_frame(*addr);
137        FrameBuilder::new(config)
138    }
139}
140
141/// A frame that is still open after run_continuable returned FrameAction::Continue.
142/// Supports chaining additional run_continuable() calls or finishing.
143pub struct ContinuableFrame<'a> {
144    frame: &'a mut Box<MockInvocationFrame>,
145}
146
147impl<'a> ContinuableFrame<'a> {
148    /// Execute another step on this frame.
149    pub fn run_continuable(
150        &mut self,
151        f: impl FnOnce(&mut FrameHandle) -> FrameAction + UnwindSafe,
152    ) -> &mut Self {
153        self.frame.execute(|inner_frame| {
154            let mut handle = FrameHandle { frame: inner_frame };
155            f(&mut handle)
156        });
157        self
158    }
159
160    /// Open a child frame from within this continuable frame.
161    pub fn child_call(
162        &mut self,
163        addr: &thruput_xdr::types::ContractAddress,
164    ) -> FrameBuilder<'_> {
165        let config = self.frame.open_child_frame(*addr);
166        FrameBuilder::new(config)
167    }
168
169    /// Extract the frame result. Call this after the frame has been closed
170    /// (i.e., after a run_continuable that returned FrameAction::Close).
171    pub fn finish(&mut self) -> FrameResult {
172        self.frame.result()
173    }
174}