multiversx_sc_snippets/
network_response.rs1use crate::sdk::{
2 data::transaction::{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_scr = tx.smart_contract_results.iter().find(is_out_scr);
61
62 if let Some(out_scr) = out_scr {
63 decode_scr_data_or_panic(&out_scr.data)
64 } else {
65 process_out_from_log(tx).unwrap_or_default()
66 }
67}
68
69fn process_logs(tx: &TransactionOnNetwork) -> Vec<Log> {
70 if let Some(api_logs) = &tx.logs {
71 return api_logs
72 .events
73 .iter()
74 .map(|event| Log {
75 address: event.address.address.clone(),
76 endpoint: event.identifier.clone(),
77 topics: extract_topics(event),
78 data: extract_data(event),
79 })
80 .collect::<Vec<Log>>();
81 }
82
83 Vec::new()
84}
85
86fn extract_data(event: &Events) -> Vec<Vec<u8>> {
87 let mut out: Vec<Vec<u8>> = Vec::new();
88 event
89 .data
90 .for_each(|data_field| out.push(data_field.clone().into_bytes()));
91 out
92}
93
94fn extract_topics(event: &Events) -> Vec<Vec<u8>> {
95 event
96 .topics
97 .clone()
98 .unwrap_or_default()
99 .into_iter()
100 .map(|s| s.into_bytes())
101 .collect()
102}
103
104fn process_out_from_log(tx: &TransactionOnNetwork) -> Option<Vec<Vec<u8>>> {
105 if let Some(logs) = &tx.logs {
106 logs.events.iter().rev().find_map(|event| {
107 if event.identifier == "writeLog" {
108 let mut out = Vec::new();
109 event.data.for_each(|data_member| {
110 let decoded_data = String::from_utf8(base64_decode(data_member)).unwrap();
111
112 if decoded_data.starts_with('@') {
113 let out_content = decode_scr_data_or_panic(decoded_data.as_str());
114 out.extend(out_content);
115 }
116 });
117 return Some(out);
118 }
119
120 None
121 })
122 } else {
123 None
124 }
125}
126
127fn process_new_deployed_address(tx: &TransactionOnNetwork) -> Option<Address> {
128 if tx.processing_type_on_destination != SC_DEPLOY_PROCESSING_TYPE {
129 return None;
130 }
131
132 let sender_address_bytes = tx.sender.address.as_bytes();
133 let sender_nonce_bytes = tx.nonce.to_le_bytes();
134 let mut bytes_to_hash: Vec<u8> = Vec::new();
135 bytes_to_hash.extend_from_slice(sender_address_bytes);
136 bytes_to_hash.extend_from_slice(&sender_nonce_bytes);
137
138 let address_keccak = keccak256(&bytes_to_hash);
139
140 let mut address = [0u8; 32];
141
142 address[0..8].copy_from_slice(&[0u8; 8]);
143 address[8..10].copy_from_slice(&[5, 0]);
144 address[10..30].copy_from_slice(&address_keccak[10..30]);
145 address[30..32].copy_from_slice(&sender_address_bytes[30..32]);
146
147 Some(Address::from(address))
148}
149
150fn process_new_issued_token_identifier(tx: &TransactionOnNetwork) -> Option<String> {
151 let original_tx_data = String::from_utf8(base64_decode(tx.data.as_ref().unwrap())).unwrap();
152
153 for scr in tx.smart_contract_results.iter() {
154 if scr.sender.address != ESDTSystemSCAddress.to_address() {
155 continue;
156 }
157
158 let prev_tx_data: &str = if let Some(prev_tx) = tx
159 .smart_contract_results
160 .iter()
161 .find(|e| e.hash == scr.prev_tx_hash)
162 {
163 prev_tx.data.as_ref()
164 } else if &scr.prev_tx_hash == tx.hash.as_ref().unwrap() {
165 &original_tx_data
166 } else {
167 continue;
168 };
169
170 let is_issue_fungible = prev_tx_data.starts_with("issue@");
171 let is_issue_semi_fungible = prev_tx_data.starts_with("issueSemiFungible@");
172 let is_issue_non_fungible = prev_tx_data.starts_with("issueNonFungible@");
173 let is_register_meta_esdt = prev_tx_data.starts_with("registerMetaESDT@");
174 let is_register_and_set_all_roles_esdt =
175 prev_tx_data.starts_with("registerAndSetAllRoles@");
176 let is_register_dynamic_esdt = prev_tx_data.starts_with("registerDynamic");
177 let is_register_and_set_all_roles_dynamic_esdt =
178 prev_tx_data.starts_with("registerAndSetAllRolesDynamic@");
179
180 if !is_issue_fungible
181 && !is_issue_semi_fungible
182 && !is_issue_non_fungible
183 && !is_register_meta_esdt
184 && !is_register_and_set_all_roles_esdt
185 && !is_register_dynamic_esdt
186 && !is_register_and_set_all_roles_dynamic_esdt
187 {
188 continue;
189 }
190
191 if scr.data.starts_with("ESDTTransfer@") {
192 let encoded_tid = scr.data.split('@').nth(1);
193 return Some(String::from_utf8(hex::decode(encoded_tid?).unwrap()).unwrap());
194 } else if scr.data.starts_with("@00@") || scr.data.starts_with("@6f6b@") {
195 let encoded_tid = scr.data.split('@').nth(2);
196 return Some(String::from_utf8(hex::decode(encoded_tid?).unwrap()).unwrap());
197 }
198 }
199 None
200}
201
202fn find_log<'a>(tx: &'a TransactionOnNetwork, log_identifier: &str) -> Option<&'a Events> {
203 if let Some(logs) = &tx.logs {
204 logs.events
205 .iter()
206 .find(|event| event.identifier == log_identifier)
207 } else {
208 None
209 }
210}
211
212pub fn process_topics_error(topics: Option<&Vec<String>>) -> Option<String> {
214 if topics.is_none() {
215 return Some("missing topics".to_string());
216 }
217
218 let topics = topics.unwrap();
219 if topics.len() != 2 {
220 Some(format!(
221 "expected to have 2 topics, found {} instead",
222 topics.len()
223 ))
224 } else {
225 None
226 }
227}
228
229pub fn decode_scr_data_or_panic(data: &str) -> Vec<Vec<u8>> {
231 let mut split = data.split('@');
232 let _ = split.next().expect("SCR data should start with '@'");
233 let result_code = split.next().expect("missing result code");
234 assert_eq!(result_code, "6f6b", "result code is not 'ok'");
235
236 split
237 .map(|encoded_arg| hex::decode(encoded_arg).expect("error hex-decoding result"))
238 .collect()
239}
240
241pub fn is_out_scr(scr: &&ApiSmartContractResult) -> bool {
243 scr.nonce != 0 && scr.data.starts_with('@')
244}