multiversx_sc_scenario/scenario/model/step/
sc_call_step.rs

1use multiversx_sc::{abi::TypeAbiFrom, types::H256};
2use unwrap_infallible::UnwrapInfallible;
3
4use crate::{
5    api::StaticApi,
6    scenario::model::{AddressValue, BigUintValue, BytesValue, TxCall, TxESDT, TxExpect, U64Value},
7    scenario_model::TxResponse,
8};
9
10use crate::multiversx_sc::{
11    codec::{PanicErrorHandler, TopEncodeMulti},
12    types::{ContractCall, ManagedArgBuffer},
13};
14
15#[derive(Debug, Clone)]
16pub struct ScCallStep {
17    pub id: String,
18    pub tx_id: Option<String>,
19    pub explicit_tx_hash: Option<H256>,
20    pub comment: Option<String>,
21    pub tx: Box<TxCall>,
22    pub expect: Option<TxExpect>,
23    pub response: Option<TxResponse>,
24}
25
26impl Default for ScCallStep {
27    fn default() -> Self {
28        Self {
29            id: Default::default(),
30            tx_id: Default::default(),
31            explicit_tx_hash: Default::default(),
32            comment: Default::default(),
33            tx: Default::default(),
34            expect: Some(TxExpect::ok()),
35            response: Default::default(),
36        }
37    }
38}
39
40impl ScCallStep {
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    pub fn from<A>(mut self, address: A) -> Self
46    where
47        AddressValue: From<A>,
48    {
49        self.tx.from = AddressValue::from(address);
50        self
51    }
52
53    pub fn to<A>(mut self, address: A) -> Self
54    where
55        AddressValue: From<A>,
56    {
57        self.tx.to = AddressValue::from(address);
58        self
59    }
60
61    pub fn egld_value<A>(mut self, amount: A) -> Self
62    where
63        BigUintValue: From<A>,
64    {
65        if !self.tx.esdt_value.is_empty() && self.tx.egld_value.value > 0u32.into() {
66            panic!("Cannot transfer both EGLD and ESDT");
67        }
68
69        self.tx.egld_value = BigUintValue::from(amount);
70        self
71    }
72
73    pub fn esdt_transfer<T, N, A>(mut self, token_id: T, token_nonce: N, amount: A) -> Self
74    where
75        BytesValue: From<T>,
76        U64Value: From<N>,
77        BigUintValue: From<A>,
78    {
79        if self.tx.egld_value.value > 0u32.into() {
80            panic!("Cannot transfer both EGLD and ESDT");
81        }
82
83        self.tx.esdt_value.push(TxESDT {
84            esdt_token_identifier: BytesValue::from(token_id),
85            nonce: U64Value::from(token_nonce),
86            esdt_value: BigUintValue::from(amount),
87        });
88
89        self
90    }
91
92    pub fn multi_esdt_transfer<T>(mut self, tokens: T) -> Self
93    where
94        T: IntoIterator<Item = TxESDT>,
95    {
96        if self.tx.egld_value.value > 0u32.into() {
97            panic!("Cannot transfer both EGLD and ESDT");
98        }
99
100        self.tx.esdt_value.extend(tokens);
101
102        self
103    }
104
105    pub fn function(mut self, expr: &str) -> Self {
106        self.tx.function = expr.to_string();
107        self
108    }
109
110    pub fn tx_hash<T>(mut self, tx_hash_expr: T) -> Self
111    where
112        H256: From<T>,
113    {
114        self.explicit_tx_hash = Some(tx_hash_expr.into());
115        self
116    }
117
118    pub fn argument<A>(mut self, expr: A) -> Self
119    where
120        BytesValue: From<A>,
121    {
122        self.tx.arguments.push(BytesValue::from(expr));
123        self
124    }
125
126    pub fn gas_limit<V>(mut self, value: V) -> Self
127    where
128        U64Value: From<V>,
129    {
130        self.tx.gas_limit = U64Value::from(value);
131        self
132    }
133
134    /// Sets following fields based on the smart contract proxy:
135    /// - "to"
136    /// - "function"
137    /// - "arguments"
138    #[deprecated(
139        since = "0.49.0",
140        note = "Please use the unified transaction syntax instead."
141    )]
142    #[allow(deprecated)]
143    pub fn call<CC>(mut self, contract_call: CC) -> super::TypedScCall<CC::OriginalResult>
144    where
145        CC: multiversx_sc::types::ContractCallBase<StaticApi>,
146    {
147        let (to_str, function, egld_value_expr, scenario_args) =
148            process_contract_call(contract_call);
149        self = self.to(to_str.as_str());
150
151        if self.tx.function.is_empty() {
152            self = self.function(function.as_str());
153        }
154        if self.tx.egld_value.value == 0u32.into() {
155            self = self.egld_value(egld_value_expr);
156        }
157        for arg in scenario_args {
158            self = self.argument(arg.as_str());
159        }
160        self.into()
161    }
162
163    /// Sets following fields based on the smart contract proxy:
164    /// - "to"
165    /// - "function"
166    /// - "arguments"
167    /// - "expect"
168    ///     - "out"
169    ///     - "status" set to 0
170    #[deprecated(
171        since = "0.42.0",
172        note = "Please use `call` followed by `expect`, there is no point in having a method that does both."
173    )]
174    #[allow(deprecated)]
175    pub fn call_expect<CC, ExpectedResult>(
176        self,
177        contract_call: CC,
178        expected_value: ExpectedResult,
179    ) -> super::TypedScCall<CC::OriginalResult>
180    where
181        CC: ContractCall<StaticApi>,
182        ExpectedResult: TypeAbiFrom<CC::OriginalResult> + TopEncodeMulti,
183    {
184        self.call(contract_call).expect_value(expected_value)
185    }
186
187    /// Adds a custom expect section to the tx.
188    pub fn expect(mut self, expect: TxExpect) -> Self {
189        self.expect = Some(expect);
190        self
191    }
192
193    /// Explicitly states that no tx expect section should be added and no checks should be performed.
194    ///
195    /// Note: by default a basic `TxExpect::ok()` is added, which checks that status is 0 and nothing else.
196    pub fn no_expect(mut self) -> Self {
197        self.expect = None;
198        self
199    }
200
201    /// Unwraps the response, if available.
202    pub fn response(&self) -> &TxResponse {
203        self.response
204            .as_ref()
205            .expect("SC call response not yet available")
206    }
207
208    pub fn save_response(&mut self, mut tx_response: TxResponse) {
209        if let Some(expect) = &mut self.expect {
210            if expect.build_from_response {
211                expect.update_from_response(&tx_response)
212            }
213        }
214        if tx_response.tx_hash.is_none() {
215            tx_response.tx_hash = self
216                .explicit_tx_hash
217                .as_ref()
218                .map(|vm_hash| vm_hash.as_array().into());
219        }
220        self.response = Some(tx_response);
221    }
222}
223
224impl AsMut<ScCallStep> for ScCallStep {
225    fn as_mut(&mut self) -> &mut ScCallStep {
226        self
227    }
228}
229
230/// Extracts
231/// - recipient,
232/// - endpoint name,
233/// - the arguments.
234#[allow(deprecated)]
235pub(super) fn process_contract_call<CC>(
236    contract_call: CC,
237) -> (String, String, BigUintValue, Vec<String>)
238where
239    CC: multiversx_sc::types::ContractCallBase<StaticApi>,
240{
241    let normalized_cc = contract_call.into_normalized();
242    let to_str = format!(
243        "0x{}",
244        hex::encode(normalized_cc.basic.to.to_address().as_bytes())
245    );
246    let function = String::from_utf8(
247        normalized_cc
248            .basic
249            .function_call
250            .function_name
251            .to_boxed_bytes()
252            .into_vec(),
253    )
254    .unwrap();
255    let egld_value_expr = BigUintValue::from(normalized_cc.egld_payment);
256    let scenario_args = convert_call_args(&normalized_cc.basic.function_call.arg_buffer);
257    (to_str, function, egld_value_expr, scenario_args)
258}
259
260pub fn convert_call_args(arg_buffer: &ManagedArgBuffer<StaticApi>) -> Vec<String> {
261    arg_buffer
262        .to_raw_args_vec()
263        .iter()
264        .map(|arg| format!("0x{}", hex::encode(arg)))
265        .collect()
266}
267
268pub(super) fn format_expect<T: TopEncodeMulti>(t: T) -> TxExpect {
269    let mut encoded = Vec::<Vec<u8>>::new();
270    t.multi_encode_or_handle_err(&mut encoded, PanicErrorHandler)
271        .unwrap_infallible();
272    let mut expect = TxExpect::ok().no_result();
273    for encoded_res in encoded {
274        let encoded_hex_string = format!("0x{}", hex::encode(encoded_res.as_slice()));
275        expect = expect.result(encoded_hex_string.as_str());
276    }
277    expect
278}