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