multiversx_sc_modules/
ongoing_operation.rs

1multiversx_sc::imports!();
2
3pub const DEFAULT_MIN_GAS_TO_SAVE_PROGRESS: u64 = 1_000_000;
4
5pub type LoopOp = bool;
6pub const CONTINUE_OP: bool = true;
7pub const STOP_OP: bool = false;
8
9#[multiversx_sc::module]
10pub trait OngoingOperationModule {
11    /// Run the given lambda function until it's either completed or it runs out of gas.
12    /// min_gas_to_save_progress should be a reasonable value to save gas.
13    /// This can vary a lot based on the given ongoing operation data structures.
14    ///
15    /// # Usage example: Counting to 100
16    /// ```
17    /// # use multiversx_sc::types::OperationCompletionStatus;
18    /// # use multiversx_sc_modules::ongoing_operation::{
19    /// #     self, CONTINUE_OP, DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, STOP_OP,
20    /// # };
21    /// # pub trait ExampleContract: multiversx_sc::contract_base::ContractBase + ongoing_operation::OngoingOperationModule
22    /// # {
23    /// fn count_to_100(&self) -> OperationCompletionStatus {
24    ///     let mut current_number = self.load_operation::<usize>();
25    ///     let run_result = self.run_while_it_has_gas(DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, || {
26    ///         if current_number == 100 {
27    ///             return STOP_OP;
28    ///         }
29    ///
30    ///         current_number += 1;
31    ///         
32    ///         CONTINUE_OP
33    ///     });
34    ///     
35    ///     if run_result == OperationCompletionStatus::InterruptedBeforeOutOfGas {
36    ///         self.save_progress(&current_number);
37    ///     }
38    ///
39    ///     run_result
40    /// }
41    /// # }
42    /// ```
43    fn run_while_it_has_gas<Process>(
44        &self,
45        min_gas_to_save_progress: u64,
46        mut process: Process,
47    ) -> OperationCompletionStatus
48    where
49        Process: FnMut() -> LoopOp,
50    {
51        let mut gas_per_iteration = 0;
52        let mut gas_before = self.blockchain().get_gas_left();
53        loop {
54            let loop_op = process();
55            if loop_op == STOP_OP {
56                break;
57            }
58
59            let gas_after = self.blockchain().get_gas_left();
60            let current_iteration_cost = gas_before - gas_after;
61            if current_iteration_cost > gas_per_iteration {
62                gas_per_iteration = current_iteration_cost;
63            }
64
65            if !self.can_continue_operation(gas_per_iteration, min_gas_to_save_progress) {
66                return OperationCompletionStatus::InterruptedBeforeOutOfGas;
67            }
68
69            gas_before = gas_after;
70        }
71
72        self.clear_operation();
73
74        OperationCompletionStatus::Completed
75    }
76
77    #[inline]
78    fn can_continue_operation(&self, operation_cost: u64, min_gas_to_save_progress: u64) -> bool {
79        let gas_left = self.blockchain().get_gas_left();
80
81        gas_left > min_gas_to_save_progress + operation_cost
82    }
83
84    /// Load the current ongoing operation.
85    /// Will return the default value if no operation is saved.
86    fn load_operation<T: TopDecode + Default>(&self) -> T {
87        let raw_buffer = self.current_ongoing_operation().get();
88        if raw_buffer.is_empty() {
89            return T::default();
90        }
91
92        match T::top_decode(raw_buffer) {
93            Result::Ok(op) => op,
94            Result::Err(err) => sc_panic!(err.message_str()),
95        }
96    }
97
98    /// Save progress for the current operation. The given value can be any serializable type.
99    fn save_progress<T: TopEncode>(&self, op: &T) {
100        let mut encoded_op = ManagedBuffer::new();
101        if let Result::Err(err) = op.top_encode(&mut encoded_op) {
102            sc_panic!(err.message_str());
103        }
104
105        self.current_ongoing_operation().set(&encoded_op);
106    }
107
108    /// Clears the currently stored operation. This is for internal use.
109    #[inline]
110    fn clear_operation(&self) {
111        self.current_ongoing_operation().clear();
112    }
113
114    #[storage_mapper("ongoing_operation:currentOngoingOperation")]
115    fn current_ongoing_operation(&self) -> SingleValueMapper<ManagedBuffer>;
116}