drt_sc_snippets/
network_response.rs

1use drt_sc_scenario::{
2    imports::{Address, DCDTSystemSCAddress},
3    drt_chain_vm::crypto_functions::keccak256,
4    scenario_model::{TxResponse, TxResponseStatus},
5};
6use drt_sdk::{
7    data::transaction::{ApiSmartContractResult, Events, TransactionOnNetwork},
8    utils::base64_decode,
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) -> TxResponse {
16    let tx_error = process_signal_error(&tx);
17    if !tx_error.is_success() {
18        return TxResponse {
19            tx_error,
20            ..Default::default()
21        };
22    }
23
24    process_success(&tx)
25}
26
27fn process_signal_error(tx: &TransactionOnNetwork) -> TxResponseStatus {
28    if let Some(event) = find_log(tx, LOG_IDENTIFIER_SIGNAL_ERROR) {
29        let topics = event.topics.as_ref();
30        if let Some(error) = process_topics_error(topics) {
31            return TxResponseStatus::signal_error(&error);
32        }
33
34        let error_raw = base64_decode(topics.unwrap().get(1).unwrap());
35        let error = String::from_utf8(error_raw).unwrap();
36        return TxResponseStatus::signal_error(&error);
37    }
38
39    TxResponseStatus::default()
40}
41
42fn process_success(tx: &TransactionOnNetwork) -> TxResponse {
43    TxResponse {
44        out: process_out(tx),
45        new_deployed_address: process_new_deployed_address(tx),
46        new_issued_token_identifier: process_new_issued_token_identifier(tx),
47        ..Default::default()
48    }
49}
50
51fn process_out(tx: &TransactionOnNetwork) -> Vec<Vec<u8>> {
52    let out_scr = tx.smart_contract_results.iter().find(is_out_scr);
53
54    if let Some(out_scr) = out_scr {
55        decode_scr_data_or_panic(&out_scr.data)
56    } else {
57        process_out_from_log(tx).unwrap_or_default()
58    }
59}
60
61fn process_out_from_log(tx: &TransactionOnNetwork) -> Option<Vec<Vec<u8>>> {
62    if let Some(logs) = &tx.logs {
63        logs.events.iter().rev().find_map(|event| {
64            if event.identifier == "writeLog" {
65                if let Some(data) = &event.data {
66                    let decoded_data = String::from_utf8(base64_decode(data)).unwrap();
67
68                    if decoded_data.starts_with('@') {
69                        let out = decode_scr_data_or_panic(decoded_data.as_str());
70                        return Some(out);
71                    }
72                }
73            }
74
75            None
76        })
77    } else {
78        None
79    }
80}
81
82fn process_new_deployed_address(tx: &TransactionOnNetwork) -> Option<Address> {
83    if tx.processing_type_on_destination != SC_DEPLOY_PROCESSING_TYPE {
84        return None;
85    }
86
87    let sender_address_bytes = tx.sender.to_bytes();
88    let sender_nonce_bytes = tx.nonce.to_le_bytes();
89    let mut bytes_to_hash: Vec<u8> = Vec::new();
90    bytes_to_hash.extend_from_slice(&sender_address_bytes);
91    bytes_to_hash.extend_from_slice(&sender_nonce_bytes);
92
93    let address_keccak = keccak256(&bytes_to_hash);
94
95    let mut address = [0u8; 32];
96
97    address[0..8].copy_from_slice(&[0u8; 8]);
98    address[8..10].copy_from_slice(&[5, 0]);
99    address[10..30].copy_from_slice(&address_keccak[10..30]);
100    address[30..32].copy_from_slice(&sender_address_bytes[30..32]);
101
102    Some(Address::from(address))
103}
104
105fn process_new_issued_token_identifier(tx: &TransactionOnNetwork) -> Option<String> {
106    for scr in tx.smart_contract_results.iter() {
107        if scr.sender.to_bech32_string().unwrap() != DCDTSystemSCAddress.to_bech32_string() {
108            continue;
109        }
110
111        let Some(prev_tx) = tx
112            .smart_contract_results
113            .iter()
114            .find(|e| e.hash == scr.prev_tx_hash)
115        else {
116            continue;
117        };
118
119        let is_issue_fungible = prev_tx.data.starts_with("issue@");
120        let is_issue_semi_fungible = prev_tx.data.starts_with("issueSemiFungible@");
121        let is_issue_non_fungible = prev_tx.data.starts_with("issueNonFungible@");
122        let is_register_meta_dcdt = prev_tx.data.starts_with("registerMetaDCDT@");
123        let is_register_and_set_all_roles_dcdt =
124            prev_tx.data.starts_with("registerAndSetAllRoles@");
125
126        if !is_issue_fungible
127            && !is_issue_semi_fungible
128            && !is_issue_non_fungible
129            && !is_register_meta_dcdt
130            && !is_register_and_set_all_roles_dcdt
131        {
132            continue;
133        }
134
135        if scr.data.starts_with("DCDTTransfer@") {
136            let encoded_tid = scr.data.split('@').nth(1);
137            return Some(String::from_utf8(hex::decode(encoded_tid?).unwrap()).unwrap());
138        } else if scr.data.starts_with("@00@") {
139            let encoded_tid = scr.data.split('@').nth(2);
140            return Some(String::from_utf8(hex::decode(encoded_tid?).unwrap()).unwrap());
141        }
142    }
143
144    None
145}
146
147fn find_log<'a>(tx: &'a TransactionOnNetwork, log_identifier: &str) -> Option<&'a Events> {
148    if let Some(logs) = &tx.logs {
149        logs.events
150            .iter()
151            .find(|event| event.identifier == log_identifier)
152    } else {
153        None
154    }
155}
156
157/// Checks for invalid topics.
158pub fn process_topics_error(topics: Option<&Vec<String>>) -> Option<String> {
159    if topics.is_none() {
160        return Some("missing topics".to_string());
161    }
162
163    let topics = topics.unwrap();
164    if topics.len() != 2 {
165        Some(format!(
166            "expected to have 2 topics, found {} instead",
167            topics.len()
168        ))
169    } else {
170        None
171    }
172}
173
174/// Decodes the data of a smart contract result.
175pub fn decode_scr_data_or_panic(data: &str) -> Vec<Vec<u8>> {
176    let mut split = data.split('@');
177    let _ = split.next().expect("SCR data should start with '@'");
178    let result_code = split.next().expect("missing result code");
179    assert_eq!(result_code, "6f6b", "result code is not 'ok'");
180
181    split
182        .map(|encoded_arg| hex::decode(encoded_arg).expect("error hex-decoding result"))
183        .collect()
184}
185
186/// Checks if the given smart contract result is an out smart contract result.
187pub fn is_out_scr(scr: &&ApiSmartContractResult) -> bool {
188    scr.nonce != 0 && scr.data.starts_with('@')
189}