multiversx_sc_snippets/
network_response.rs

1use 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
14/// Creates a [`TxResponse`] from a [`TransactionOnNetwork`].
15pub 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
211/// Checks for invalid topics.
212pub 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
228/// Decodes the data of a smart contract result.
229pub 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
240/// Decodes the data of a multi transfer result.
241pub 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
270/// Checks if the given smart contract result is an out smart contract result.
271pub fn is_out_scr(scr: &&ApiSmartContractResult) -> bool {
272    scr.nonce != 0 && scr.data.starts_with('@')
273}
274
275/// Checks if the given smart contract result is a multi transfer smart contract result.
276pub fn is_multi_transfer(scr: &&ApiSmartContractResult) -> bool {
277    scr.data.starts_with("MultiESDTNFTTransfer@")
278}