multiversx_sc_snippets/
network_response.rs1use crate::sdk::{
2 data::transaction::{ApiLogs, ApiSmartContractResult, Events, TransactionOnNetwork},
3 utils::base64_decode,
4};
5use multiversx_sc_scenario::{
6 imports::{Address, ESDTSystemSCAddress, ReturnCode},
7 multiversx_chain_vm::{crypto_functions::keccak256, types::H256},
8 scenario_model::{Log, TxResponse, TxResponseStatus},
9};
10
11const SC_DEPLOY_PROCESSING_TYPE: &str = "SCDeployment";
12const LOG_IDENTIFIER_SIGNAL_ERROR: &str = "signalError";
13
14pub fn parse_tx_response(tx: TransactionOnNetwork, return_code: ReturnCode) -> TxResponse {
16 let tx_error = process_signal_error(&tx, return_code);
17 if !tx_error.is_success() {
18 return TxResponse {
19 tx_error,
20 tx_hash: process_tx_hash(&tx),
21 ..Default::default()
22 };
23 }
24
25 process_success(&tx)
26}
27
28fn process_signal_error(tx: &TransactionOnNetwork, return_code: ReturnCode) -> TxResponseStatus {
29 if let Some(event) = find_log(tx, LOG_IDENTIFIER_SIGNAL_ERROR) {
30 if event.topics.len() >= 2 {
31 let error_message = String::from_utf8(base64_decode(&event.topics[1])).expect(
32 "Failed to decode base64-encoded error message from transaction event topic",
33 );
34 return TxResponseStatus::new(return_code, &error_message);
35 }
36 }
37
38 TxResponseStatus::default()
39}
40
41fn process_success(tx: &TransactionOnNetwork) -> TxResponse {
42 TxResponse {
43 out: process_out(tx),
44 new_deployed_address: process_new_deployed_address(tx),
45 new_issued_token_identifier: process_new_issued_token_identifier(tx),
46 logs: process_logs(tx),
47 tx_hash: process_tx_hash(tx),
48 gas_used: tx.gas_used,
49 ..Default::default()
50 }
51}
52
53fn process_tx_hash(tx: &TransactionOnNetwork) -> Option<H256> {
54 tx.hash.as_ref().map(|encoded_hash| {
55 let decoded = hex::decode(encoded_hash).expect("error decoding tx hash from hex");
56 assert_eq!(decoded.len(), 32);
57 H256::from_slice(&decoded)
58 })
59}
60
61fn process_out(tx: &TransactionOnNetwork) -> Vec<Vec<u8>> {
62 let out_multi_transfer = tx.smart_contract_results.iter().find(is_multi_transfer);
63 let out_scr = tx.smart_contract_results.iter().find(is_out_scr);
64
65 if let Some(out_multi_transfer) = out_multi_transfer {
66 log::trace!("Parsing result from multi transfer: {out_multi_transfer:?}");
67 if let Some(data) = decode_multi_transfer_data_or_panic(out_multi_transfer.logs.clone()) {
68 return data;
69 }
70 }
71
72 if let Some(out_scr) = out_scr {
73 log::trace!("Parsing result from scr: {out_scr:?}");
74 return decode_scr_data_or_panic(&out_scr.data);
75 }
76
77 log::trace!("Parsing result from logs");
78 process_out_from_log(tx).unwrap_or_default()
79}
80
81fn process_logs(tx: &TransactionOnNetwork) -> Vec<Log> {
82 if let Some(api_logs) = &tx.logs {
83 return api_logs
84 .events
85 .iter()
86 .map(|event| Log {
87 address: event.address.address.clone(),
88 endpoint: event.identifier.clone(),
89 topics: extract_topics(event),
90 data: extract_data(event),
91 })
92 .collect::<Vec<Log>>();
93 }
94
95 Vec::new()
96}
97
98fn extract_data(event: &Events) -> Vec<Vec<u8>> {
99 let mut out: Vec<Vec<u8>> = Vec::new();
100 event
101 .data
102 .for_each(|data_field| out.push(data_field.clone().into_bytes()));
103 out
104}
105
106fn extract_topics(event: &Events) -> Vec<Vec<u8>> {
107 event
108 .topics
109 .clone()
110 .into_iter()
111 .map(|s| s.into_bytes())
112 .collect()
113}
114
115fn process_out_from_log(tx: &TransactionOnNetwork) -> Option<Vec<Vec<u8>>> {
116 if let Some(logs) = &tx.logs {
117 logs.events.iter().rev().find_map(|event| {
118 if event.identifier == "writeLog" {
119 let out = extract_write_log_data(event);
120 return Some(out);
121 }
122
123 None
124 })
125 } else {
126 None
127 }
128}
129
130fn process_new_deployed_address(tx: &TransactionOnNetwork) -> Option<Address> {
131 if tx.processing_type_on_destination != SC_DEPLOY_PROCESSING_TYPE {
132 return None;
133 }
134
135 let sender_address_bytes = tx.sender.address.as_bytes();
136 let sender_nonce_bytes = tx.nonce.to_le_bytes();
137 let mut bytes_to_hash: Vec<u8> = Vec::new();
138 bytes_to_hash.extend_from_slice(sender_address_bytes);
139 bytes_to_hash.extend_from_slice(&sender_nonce_bytes);
140
141 let address_keccak = keccak256(&bytes_to_hash);
142
143 let mut address = [0u8; 32];
144
145 address[0..8].copy_from_slice(&[0u8; 8]);
146 address[8..10].copy_from_slice(&[5, 0]);
147 address[10..30].copy_from_slice(&address_keccak[10..30]);
148 address[30..32].copy_from_slice(&sender_address_bytes[30..32]);
149
150 Some(Address::from(address))
151}
152
153fn process_new_issued_token_identifier(tx: &TransactionOnNetwork) -> Option<String> {
154 let original_tx_data = String::from_utf8(base64_decode(tx.data.as_ref().unwrap())).unwrap();
155
156 for scr in tx.smart_contract_results.iter() {
157 if scr.sender.address != ESDTSystemSCAddress.to_address() {
158 continue;
159 }
160
161 let prev_tx_data: &str = if let Some(prev_tx) = tx
162 .smart_contract_results
163 .iter()
164 .find(|e| e.hash == scr.prev_tx_hash)
165 {
166 prev_tx.data.as_ref()
167 } else if &scr.prev_tx_hash == tx.hash.as_ref().unwrap() {
168 &original_tx_data
169 } else {
170 continue;
171 };
172
173 let is_issue_fungible = prev_tx_data.starts_with("issue@");
174 let is_issue_semi_fungible = prev_tx_data.starts_with("issueSemiFungible@");
175 let is_issue_non_fungible = prev_tx_data.starts_with("issueNonFungible@");
176 let is_register_meta_esdt = prev_tx_data.starts_with("registerMetaESDT@");
177 let is_register_and_set_all_roles_esdt =
178 prev_tx_data.starts_with("registerAndSetAllRoles@");
179 let is_register_dynamic_esdt = prev_tx_data.starts_with("registerDynamic");
180 let is_register_and_set_all_roles_dynamic_esdt =
181 prev_tx_data.starts_with("registerAndSetAllRolesDynamic@");
182
183 if !is_issue_fungible
184 && !is_issue_semi_fungible
185 && !is_issue_non_fungible
186 && !is_register_meta_esdt
187 && !is_register_and_set_all_roles_esdt
188 && !is_register_dynamic_esdt
189 && !is_register_and_set_all_roles_dynamic_esdt
190 {
191 continue;
192 }
193
194 if scr.data.starts_with("ESDTTransfer@") {
195 let encoded_tid = scr.data.split('@').nth(1);
196 return Some(String::from_utf8(hex::decode(encoded_tid?).unwrap()).unwrap());
197 } else if scr.data.starts_with("@00@") || scr.data.starts_with("@6f6b@") {
198 let encoded_tid = scr.data.split('@').nth(2);
199 return Some(String::from_utf8(hex::decode(encoded_tid?).unwrap()).unwrap());
200 }
201 }
202 None
203}
204
205fn find_log<'a>(tx: &'a TransactionOnNetwork, log_identifier: &str) -> Option<&'a Events> {
206 if let Some(logs) = &tx.logs {
207 logs.events
208 .iter()
209 .find(|event| event.identifier == log_identifier)
210 } else {
211 None
212 }
213}
214
215pub fn process_topics_error(topics: Option<&Vec<String>>) -> Option<String> {
217 if topics.is_none() {
218 return Some("missing topics".to_string());
219 }
220
221 let topics = topics.unwrap();
222 if topics.len() != 2 {
223 Some(format!(
224 "expected to have 2 topics, found {} instead",
225 topics.len()
226 ))
227 } else {
228 None
229 }
230}
231
232pub fn decode_scr_data_or_panic(data: &str) -> Vec<Vec<u8>> {
234 let mut split = data.split('@');
235 let _ = split.next().expect("SCR data should start with '@'");
236 let result_code = split.next().expect("missing result code");
237 assert_eq!(result_code, "6f6b", "result code is not 'ok'");
238
239 split
240 .map(|encoded_arg| hex::decode(encoded_arg).expect("error hex-decoding result"))
241 .collect()
242}
243
244pub fn decode_multi_transfer_data_or_panic(logs: Option<ApiLogs>) -> Option<Vec<Vec<u8>>> {
246 let logs = logs?;
247
248 if let Some(event) = logs
249 .events
250 .iter()
251 .find(|event| event.identifier == "writeLog")
252 {
253 let out = extract_write_log_data(event);
254 return Some(out);
255 }
256
257 None
258}
259
260fn extract_write_log_data(event: &Events) -> Vec<Vec<u8>> {
261 let mut out = Vec::new();
262 event.data.for_each(|data_member| {
263 let decoded_data = String::from_utf8(base64_decode(data_member)).unwrap();
264
265 if decoded_data.starts_with('@') {
266 let out_content = decode_scr_data_or_panic(decoded_data.as_str());
267 out.extend(out_content);
268 }
269 });
270
271 out
272}
273
274pub fn is_out_scr(scr: &&ApiSmartContractResult) -> bool {
276 scr.nonce != 0 && scr.data.starts_with('@')
277}
278
279pub fn is_multi_transfer(scr: &&ApiSmartContractResult) -> bool {
281 scr.data.starts_with("MultiESDTNFTTransfer@")
282}