1use cardano_serialization_lib::{self as csl};
2use pallas_codec::minicbor::data::Tag;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use whisky_common::{
6 Budget, DatumSource, InlineDatumSource, InlineScriptSource, InlineSimpleScriptSource,
7 LanguageVersion, ProvidedDatumSource, ProvidedScriptSource, ProvidedSimpleScriptSource,
8 Redeemer, RefTxIn, UTxO, UtxoInput, WError,
9};
10
11use pallas_codec::minicbor::Encoder;
12
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
14pub struct ScriptWitness {
15 pub datums: HashMap<String, DatumSource>,
16 pub redeemers: HashMap<RedeemerIndex, Redeemer>,
17 pub scripts: HashMap<csl::ScriptHash, Script>,
18}
19
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct ParserContext {
22 pub resolved_utxos: HashMap<csl::TransactionInput, UTxO>,
23 pub script_witness: ScriptWitness,
24}
25
26impl ParserContext {
27 pub fn new() -> Self {
28 Self {
29 resolved_utxos: HashMap::new(),
30 script_witness: ScriptWitness {
31 datums: HashMap::new(),
32 redeemers: HashMap::new(),
33 scripts: HashMap::new(),
34 },
35 }
36 }
37
38 pub fn fill_resolved_utxos(
39 &mut self,
40 tx_body: &csl::TransactionBody,
41 resolved_utxos: &[UTxO],
42 ) -> Result<(), String> {
43 let inputs = tx_body.inputs();
44 let ref_inputs = tx_body.reference_inputs();
45 let collateral_inputs = tx_body.collateral();
46 let all_transaction_inputs: Vec<csl::TransactionInput> = inputs
47 .into_iter()
48 .chain(
49 ref_inputs
50 .unwrap_or(csl::TransactionInputs::new())
51 .into_iter(),
52 )
53 .chain(
54 collateral_inputs
55 .unwrap_or(csl::TransactionInputs::new())
56 .into_iter(),
57 )
58 .map(|input| input.clone())
59 .collect();
60
61 let utxo_map = resolved_utxos
62 .iter()
63 .map(|utxo| (utxo.input.clone(), utxo.clone()))
64 .collect::<HashMap<UtxoInput, UTxO>>();
65
66 for input in all_transaction_inputs {
67 let tx_hash = input.transaction_id().to_hex();
68 let index = input.index();
69 let utxo = utxo_map.get(&UtxoInput {
70 tx_hash,
71 output_index: index,
72 });
73 if let Some(utxo) = utxo {
74 self.resolved_utxos.insert(input, utxo.clone());
75 }
76 }
77 Ok(())
78 }
79
80 pub fn collect_script_witnesses_from_tx_witnesses_set(
81 &mut self,
82 tx_witnesses_set: csl::TransactionWitnessSet,
83 ) -> Result<(), String> {
84 let mut datums = HashMap::new();
85 let mut redeemers = HashMap::new();
86 let mut scripts = HashMap::new();
87
88 if let Some(plutus_data_list) = tx_witnesses_set.plutus_data() {
89 for i in 0..plutus_data_list.len() {
90 let plutus_data = plutus_data_list.get(i);
91 let data_hash = csl::hash_plutus_data(&plutus_data).to_hex();
92 let datum_source = DatumSource::ProvidedDatumSource(ProvidedDatumSource {
93 data: plutus_data.to_hex(),
94 });
95 datums.insert(data_hash, datum_source);
96 }
97 }
98
99 if let Some(redeemer_list) = tx_witnesses_set.redeemers() {
100 for i in 0..redeemer_list.len() {
101 let redeemer = redeemer_list.get(i);
102 let tag = redeemer.tag().kind();
103 let index = redeemer
104 .index()
105 .to_string()
106 .parse::<usize>()
107 .map_err(|e| format!("Failed to parse redeemer index: {:?}", e))?;
108 let redeemer_index = match tag {
109 csl::RedeemerTagKind::Spend => RedeemerIndex::Spend(index),
110 csl::RedeemerTagKind::Mint => RedeemerIndex::Mint(index),
111 csl::RedeemerTagKind::Cert => RedeemerIndex::Cert(index),
112 csl::RedeemerTagKind::Reward => RedeemerIndex::Reward(index),
113 csl::RedeemerTagKind::Vote => RedeemerIndex::Vote(index),
114 csl::RedeemerTagKind::VotingProposal => RedeemerIndex::VotingProposal(index),
115 };
116 let whisky_redeemer = csl_redeemer_to_redeemer(redeemer);
117 redeemers.insert(redeemer_index, whisky_redeemer);
118 }
119 }
120
121 if let Some(native_scripts) = tx_witnesses_set.native_scripts() {
122 for i in 0..native_scripts.len() {
123 let script = native_scripts.get(i);
124 let script_hash = script.hash();
125 scripts.insert(
126 script_hash,
127 Script::ProvidedNative(csl_native_script_to_native_script(script)),
128 );
129 }
130 }
131
132 if let Some(plutus_scripts) = tx_witnesses_set.plutus_scripts() {
133 for i in 0..plutus_scripts.len() {
134 let script = plutus_scripts.get(i);
135 let script_hash = script.hash();
136 scripts.insert(
137 script_hash,
138 Script::ProvidedPlutus(csl_plutus_script_to_script(script)),
139 );
140 }
141 }
142
143 self.script_witness.datums.extend(datums);
144 self.script_witness.redeemers.extend(redeemers);
145 self.script_witness.scripts.extend(scripts);
146 Ok(())
147 }
148
149 pub fn collect_script_witnesses_from_tx_body(
150 &mut self,
151 tx_body: csl::TransactionBody,
152 ) -> Result<(), WError> {
153 let inputs = tx_body.inputs();
154 let ref_inputs = tx_body.reference_inputs();
155 let all_transaction_inputs: Vec<csl::TransactionInput> = inputs
156 .into_iter()
157 .chain(
158 ref_inputs
159 .unwrap_or(csl::TransactionInputs::new())
160 .into_iter(),
161 )
162 .map(|input| input.clone())
163 .collect();
164
165 for input in all_transaction_inputs {
166 let utxo = self.resolved_utxos.get(&input).unwrap();
167 let (simple_script_source, plutus_script_source, datum_source) =
168 utxo_to_inline_sources(utxo).map_err(WError::from_err("utxo_to_inline_sources"))?;
169 if let Some((datum_source, datum_hash)) = datum_source {
170 self.script_witness
171 .datums
172 .insert(datum_hash, DatumSource::InlineDatumSource(datum_source));
173 }
174 if let Some((script_source, script_hash)) = simple_script_source {
175 self.script_witness
176 .scripts
177 .insert(script_hash, Script::ReferencedNative(script_source));
178 }
179 if let Some((script_source, script_hash)) = plutus_script_source {
180 self.script_witness
181 .scripts
182 .insert(script_hash, Script::ReferencedPlutus(script_source));
183 }
184 }
185
186 Ok(())
187 }
188}
189
190impl ScriptWitness {
191 pub fn new() -> Self {
192 Self {
193 datums: HashMap::new(),
194 redeemers: HashMap::new(),
195 scripts: HashMap::new(),
196 }
197 }
198
199 pub fn extend(&mut self, other: ScriptWitness) {
200 self.datums.extend(other.datums);
201 self.redeemers.extend(other.redeemers);
202 self.scripts.extend(other.scripts);
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
207pub enum RedeemerIndex {
208 Spend(usize),
209 Mint(usize),
210 Cert(usize),
211 Reward(usize),
212 Vote(usize),
213 VotingProposal(usize),
214}
215
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub enum Script {
218 ProvidedNative(ProvidedSimpleScriptSource),
219 ProvidedPlutus(ProvidedScriptSource),
220 ReferencedNative(InlineSimpleScriptSource),
221 ReferencedPlutus(InlineScriptSource),
222}
223
224fn csl_redeemer_to_redeemer(redeemer: csl::Redeemer) -> Redeemer {
225 Redeemer {
226 data: redeemer.data().to_hex(),
227 ex_units: Budget {
228 mem: redeemer.ex_units().mem().to_str().parse::<u64>().unwrap(),
229 steps: redeemer.ex_units().steps().to_str().parse::<u64>().unwrap(),
230 },
231 }
232}
233
234fn csl_plutus_script_to_script(script: csl::PlutusScript) -> ProvidedScriptSource {
235 ProvidedScriptSource {
236 script_cbor: script.to_hex(),
237 language_version: csl_language_version_to_language_version(
238 script.language_version().kind(),
239 ),
240 }
241}
242
243fn csl_native_script_to_native_script(script: csl::NativeScript) -> ProvidedSimpleScriptSource {
244 ProvidedSimpleScriptSource {
245 script_cbor: script.to_hex(),
246 }
247}
248
249fn utxo_to_inline_sources(
250 utxo: &UTxO,
251) -> Result<
252 (
253 Option<(InlineSimpleScriptSource, csl::ScriptHash)>,
254 Option<(InlineScriptSource, csl::ScriptHash)>,
255 Option<(InlineDatumSource, String)>,
256 ),
257 String,
258> {
259 let csl_script_ref = if let Some(script_ref) = &utxo.output.script_ref {
260 Some(normalize_script_ref(script_ref, &utxo.input)?)
261 } else {
262 None
263 };
264
265 let script_size = utxo
266 .output
267 .script_ref
268 .as_ref()
269 .map_or(0, |script_ref| script_ref.len() / 2);
270
271 let ref_tx_in = RefTxIn {
272 tx_hash: utxo.input.tx_hash.clone(),
273 tx_index: utxo.input.output_index,
274 script_size: Some(script_size),
275 };
276
277 let simple_script_source = if let Some(ref csl_script_ref) = csl_script_ref {
278 if csl_script_ref.is_native_script() {
279 let simple_script_hash = get_script_hash_from_script_ref(csl_script_ref);
280 Some((
281 InlineSimpleScriptSource {
282 ref_tx_in: ref_tx_in.clone(),
283 simple_script_hash: simple_script_hash.to_hex(),
284 script_size,
285 },
286 simple_script_hash,
287 ))
288 } else {
289 None
290 }
291 } else {
292 None
293 };
294
295 let plutus_script_source = if let Some(ref csl_script_ref) = csl_script_ref {
296 if csl_script_ref.is_plutus_script() {
297 let plutus_script = csl_script_ref.plutus_script().unwrap();
298
299 let script_hash = get_script_hash_from_script_ref(csl_script_ref);
300 Some((
301 InlineScriptSource {
302 ref_tx_in: ref_tx_in.clone(),
303 script_hash: script_hash.to_hex(),
304 script_size,
305 language_version: csl_language_version_to_language_version(
306 plutus_script.language_version().kind(),
307 ),
308 },
309 script_hash,
310 ))
311 } else {
312 None
313 }
314 } else {
315 None
316 };
317
318 let datum_source = if let Some(datum) = &utxo.output.plutus_data {
319 let data_hash = get_datum_hash_from_datum(datum, &utxo.output.data_hash)?;
320 Some((
321 InlineDatumSource {
322 tx_hash: utxo.input.tx_hash.clone(),
323 tx_index: utxo.input.output_index,
324 },
325 data_hash.to_hex(),
326 ))
327 } else {
328 None
329 };
330
331 Ok((simple_script_source, plutus_script_source, datum_source))
332}
333
334fn get_script_hash_from_script_ref(script_ref: &csl::ScriptRef) -> csl::ScriptHash {
335 if let Some(plutus_script) = script_ref.plutus_script() {
336 plutus_script.hash()
337 } else {
338 script_ref.native_script().unwrap().hash()
339 }
340}
341
342fn get_datum_hash_from_datum(
343 datum: &String,
344 datum_hash: &Option<String>,
345) -> Result<csl::DataHash, String> {
346 if let Some(datum_hash) = datum_hash {
347 csl::DataHash::from_hex(datum_hash)
348 .map_err(|e| format!("Failed to parse datum hash: {:?}", e))
349 } else {
350 let datum = csl::PlutusData::from_hex(datum)
351 .map_err(|e| format!("Failed to parse datum: {:?}", e))?;
352 Ok(csl::hash_plutus_data(&datum))
353 }
354}
355
356fn csl_language_version_to_language_version(
357 language_version: csl::LanguageKind,
358) -> LanguageVersion {
359 match language_version {
360 csl::LanguageKind::PlutusV1 => LanguageVersion::V1,
361 csl::LanguageKind::PlutusV2 => LanguageVersion::V2,
362 csl::LanguageKind::PlutusV3 => LanguageVersion::V3,
363 }
364}
365
366fn normalize_script_ref(
367 script_ref: &String,
368 tx_input: &UtxoInput,
369) -> Result<csl::ScriptRef, String> {
370 if script_ref.starts_with("82") {
371 let bytes = hex::decode(script_ref.clone())
372 .map_err(|e| format!("Failed to decode script ref hex: {:?}", e))?;
373 let mut encoder = Encoder::new(Vec::new());
374 encoder
375 .tag(Tag::new(24))
376 .map_err(|_| "Failed to write tag")?;
377 encoder
378 .bytes(&bytes)
379 .map_err(|e| format!("Failed to encode script ref bytes: {:?}", e))?;
380 let write_buffer = encoder.writer().clone();
381 csl::ScriptRef::from_bytes(write_buffer)
382 .map_err(|e| format!("Failed to decode script ref hex: {:?}", e))
383 } else {
384 csl::ScriptRef::from_hex(&script_ref).map_err(|e| {
385 format!(
386 "Failed to parse script ref: {:?} - {}#{} - with ref: {:?}",
387 e, tx_input.tx_hash, tx_input.output_index, script_ref
388 )
389 })
390 }
391}