multiversx_sc_scenario/scenario/model/step/
sc_call_step.rs1use 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 #[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 #[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 pub fn expect(mut self, expect: TxExpect) -> Self {
189 self.expect = Some(expect);
190 self
191 }
192
193 pub fn no_expect(mut self) -> Self {
197 self.expect = None;
198 self
199 }
200
201 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#[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}