multiversx_sc_modules/
ongoing_operation.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
multiversx_sc::imports!();

pub const DEFAULT_MIN_GAS_TO_SAVE_PROGRESS: u64 = 1_000_000;

pub type LoopOp = bool;
pub const CONTINUE_OP: bool = true;
pub const STOP_OP: bool = false;

#[multiversx_sc::module]
pub trait OngoingOperationModule {
    /// Run the given lambda function until it's either completed or it runs out of gas.
    /// min_gas_to_save_progress should be a reasonable value to save gas.
    /// This can vary a lot based on the given ongoing operation data structures.
    ///
    /// # Usage example: Counting to 100
    /// ```
    /// # use multiversx_sc::types::OperationCompletionStatus;
    /// # use multiversx_sc_modules::ongoing_operation::{
    /// #     self, CONTINUE_OP, DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, STOP_OP,
    /// # };
    /// # pub trait ExampleContract: multiversx_sc::contract_base::ContractBase + ongoing_operation::OngoingOperationModule
    /// # {
    /// fn count_to_100(&self) -> OperationCompletionStatus {
    ///     let mut current_number = self.load_operation::<usize>();
    ///     let run_result = self.run_while_it_has_gas(DEFAULT_MIN_GAS_TO_SAVE_PROGRESS, || {
    ///         if current_number == 100 {
    ///             return STOP_OP;
    ///         }
    ///
    ///         current_number += 1;
    ///         
    ///         CONTINUE_OP
    ///     });
    ///     
    ///     if run_result == OperationCompletionStatus::InterruptedBeforeOutOfGas {
    ///         self.save_progress(&current_number);
    ///     }
    ///
    ///     run_result
    /// }
    /// # }
    /// ```
    fn run_while_it_has_gas<Process>(
        &self,
        min_gas_to_save_progress: u64,
        mut process: Process,
    ) -> OperationCompletionStatus
    where
        Process: FnMut() -> LoopOp,
    {
        let mut gas_per_iteration = 0;
        let mut gas_before = self.blockchain().get_gas_left();
        loop {
            let loop_op = process();
            if loop_op == STOP_OP {
                break;
            }

            let gas_after = self.blockchain().get_gas_left();
            let current_iteration_cost = gas_before - gas_after;
            if current_iteration_cost > gas_per_iteration {
                gas_per_iteration = current_iteration_cost;
            }

            if !self.can_continue_operation(gas_per_iteration, min_gas_to_save_progress) {
                return OperationCompletionStatus::InterruptedBeforeOutOfGas;
            }

            gas_before = gas_after;
        }

        self.clear_operation();

        OperationCompletionStatus::Completed
    }

    #[inline]
    fn can_continue_operation(&self, operation_cost: u64, min_gas_to_save_progress: u64) -> bool {
        let gas_left = self.blockchain().get_gas_left();

        gas_left > min_gas_to_save_progress + operation_cost
    }

    /// Load the current ongoing operation.
    /// Will return the default value if no operation is saved.
    fn load_operation<T: TopDecode + Default>(&self) -> T {
        let raw_buffer = self.current_ongoing_operation().get();
        if raw_buffer.is_empty() {
            return T::default();
        }

        match T::top_decode(raw_buffer) {
            Result::Ok(op) => op,
            Result::Err(err) => sc_panic!(err.message_str()),
        }
    }

    /// Save progress for the current operation. The given value can be any serializable type.
    fn save_progress<T: TopEncode>(&self, op: &T) {
        let mut encoded_op = ManagedBuffer::new();
        if let Result::Err(err) = op.top_encode(&mut encoded_op) {
            sc_panic!(err.message_str());
        }

        self.current_ongoing_operation().set(&encoded_op);
    }

    /// Clears the currently stored operation. This is for internal use.
    #[inline]
    fn clear_operation(&self) {
        self.current_ongoing_operation().clear();
    }

    #[storage_mapper("ongoing_operation:currentOngoingOperation")]
    fn current_ongoing_operation(&self) -> SingleValueMapper<ManagedBuffer>;
}