1use std::collections::BTreeMap;
2
3use super::{CostModel, PlutusData, Redeemers, WitnessSet};
4use pallas_codec::minicbor::{self, Encode};
5use pallas_codec::utils::{KeepRaw, NonEmptySet};
6use serde::{Deserialize, Serialize};
7
8pub type PlutusVersion = u8;
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
11pub struct LanguageViews(pub BTreeMap<PlutusVersion, CostModel>);
12
13impl FromIterator<(PlutusVersion, CostModel)> for LanguageViews {
14 fn from_iter<I: IntoIterator<Item = (PlutusVersion, CostModel)>>(iter: I) -> Self {
15 Self(iter.into_iter().collect())
16 }
17}
18
19impl<C> Encode<C> for LanguageViews {
20 fn encode<W: minicbor::encode::Write>(
21 &self,
22 e: &mut minicbor::Encoder<W>,
23 ctx: &mut C,
24 ) -> Result<(), minicbor::encode::Error<W::Error>> {
25 let order: Vec<u8> = self.0.keys().copied().collect();
26 let mut canonical_order: Vec<u8> = order.into_iter().filter(|&k| k != 0).collect();
27 canonical_order.sort();
28 if self.0.contains_key(&0) {
30 canonical_order.push(0);
31 }
32
33 e.map(self.0.len() as u64)?;
34 for lang in canonical_order {
35 let cost_model = self.0.get(&lang).unwrap();
36 match lang {
37 0 => {
38 let mut inner = vec![];
39 let mut sub = minicbor::Encoder::new(&mut inner);
40 sub.begin_array().unwrap();
41 for v in cost_model.iter() {
42 sub.encode_with(v, ctx).unwrap();
43 }
44 sub.end().unwrap();
45 e.bytes(&minicbor::to_vec(0).unwrap())?;
46 e.bytes(&inner)?;
47 }
48 _ => {
49 e.encode(lang)?;
50 e.encode(cost_model)?;
51 }
52 }
53 }
54 Ok(())
55 }
56}
57
58#[derive(Debug, Clone)]
59pub struct ScriptData<'b> {
60 pub redeemers: Option<Redeemers>,
61 pub datums: Option<KeepRaw<'b, NonEmptySet<KeepRaw<'b, PlutusData>>>>,
62 pub language_views: Option<LanguageViews>,
63}
64
65impl ScriptData<'_> {
66 pub fn hash(&self) -> pallas_crypto::hash::Hash<32> {
67 let mut buf = vec![];
68
69 if let Some(redeemers) = &self.redeemers {
70 minicbor::encode(redeemers, &mut buf).unwrap(); } else {
72 buf.push(0xa0);
73 }
74
75 if let Some(datums) = &self.datums {
76 minicbor::encode(datums, &mut buf).unwrap(); }
78
79 if let Some(language_views) = &self.language_views {
80 minicbor::encode(language_views, &mut buf).unwrap(); } else {
82 buf.push(0xa0);
83 }
84
85 pallas_crypto::hash::Hasher::<256>::hash(&buf)
86 }
87}
88
89impl<'b> ScriptData<'b> {
90 pub fn build_for(
91 witness: &WitnessSet<'b>,
92 language_views_opt: &Option<LanguageViews>,
93 ) -> Option<Self> {
94 let redeemers = witness.redeemer.as_ref().map(|x| x.to_owned().unwrap());
95 let datums = witness.plutus_data.clone();
96
97 if redeemers.is_none() && datums.is_none() {
98 return None;
99 }
100
101 let language_views = if redeemers.is_some() && language_views_opt.is_some() {
102 language_views_opt.clone()
103 } else {
104 None
105 };
106
107 Some(ScriptData {
108 redeemers,
109 datums,
110 language_views,
111 })
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use std::sync::LazyLock;
118
119 use crate::conway::Tx;
120
121 use super::*;
122
123 static COST_MODEL_PLUTUS_V1: LazyLock<Vec<i64>> = LazyLock::new(|| {
124 vec![
125 100788, 420, 1, 1, 1000, 173, 0, 1, 1000, 59957, 4, 1, 11183, 32, 201305, 8356, 4,
126 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 100, 100,
127 16000, 100, 94375, 32, 132994, 32, 61462, 4, 72010, 178, 0, 1, 22151, 32, 91189, 769,
128 4, 2, 85848, 228465, 122, 0, 1, 1, 1000, 42921, 4, 2, 24548, 29498, 38, 1, 898148,
129 27279, 1, 51775, 558, 1, 39184, 1000, 60594, 1, 141895, 32, 83150, 32, 15299, 32,
130 76049, 1, 13169, 4, 22100, 10, 28999, 74, 1, 28999, 74, 1, 43285, 552, 1, 44749, 541,
131 1, 33852, 32, 68246, 32, 72362, 32, 7243, 32, 7391, 32, 11546, 32, 85848, 228465, 122,
132 0, 1, 1, 90434, 519, 0, 1, 74433, 32, 85848, 228465, 122, 0, 1, 1, 85848, 228465, 122,
133 0, 1, 1, 270652, 22588, 4, 1457325, 64566, 4, 20467, 1, 4, 0, 141992, 32, 100788, 420,
134 1, 1, 81663, 32, 59498, 32, 20142, 32, 24588, 32, 20744, 32, 25933, 32, 24623, 32,
135 53384111, 14333, 10,
136 ]
137 });
138
139 static COST_MODEL_PLUTUS_V2: LazyLock<Vec<i64>> = LazyLock::new(|| {
140 vec![
141 100788, 420, 1, 1, 1000, 173, 0, 1, 1000, 59957, 4, 1, 11183, 32, 201305, 8356, 4,
142 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 100, 100,
143 16000, 100, 94375, 32, 132994, 32, 61462, 4, 72010, 178, 0, 1, 22151, 32, 91189, 769,
144 4, 2, 85848, 228465, 122, 0, 1, 1, 1000, 42921, 4, 2, 24548, 29498, 38, 1, 898148,
145 27279, 1, 51775, 558, 1, 39184, 1000, 60594, 1, 141895, 32, 83150, 32, 15299, 32,
146 76049, 1, 13169, 4, 22100, 10, 28999, 74, 1, 28999, 74, 1, 43285, 552, 1, 44749, 541,
147 1, 33852, 32, 68246, 32, 72362, 32, 7243, 32, 7391, 32, 11546, 32, 85848, 228465, 122,
148 0, 1, 1, 90434, 519, 0, 1, 74433, 32, 85848, 228465, 122, 0, 1, 1, 85848, 228465, 122,
149 0, 1, 1, 955506, 213312, 0, 2, 270652, 22588, 4, 1457325, 64566, 4, 20467, 1, 4, 0,
150 141992, 32, 100788, 420, 1, 1, 81663, 32, 59498, 32, 20142, 32, 24588, 32, 20744, 32,
151 25933, 32, 24623, 32, 43053543, 10, 53384111, 14333, 10, 43574283, 26308, 10,
152 ]
153 });
154
155 static COST_MODEL_PLUTUS_V3: LazyLock<Vec<i64>> = LazyLock::new(|| {
156 vec![
157 100788, 420, 1, 1, 1000, 173, 0, 1, 1000, 59957, 4, 1, 11183, 32, 201305, 8356, 4,
158 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 16000, 100, 100, 100,
159 16000, 100, 94375, 32, 132994, 32, 61462, 4, 72010, 178, 0, 1, 22151, 32, 91189, 769,
160 4, 2, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 1, 1000, 42921, 4, 2,
161 24548, 29498, 38, 1, 898148, 27279, 1, 51775, 558, 1, 39184, 1000, 60594, 1, 141895,
162 32, 83150, 32, 15299, 32, 76049, 1, 13169, 4, 22100, 10, 28999, 74, 1, 28999, 74, 1,
163 43285, 552, 1, 44749, 541, 1, 33852, 32, 68246, 32, 72362, 32, 7243, 32, 7391, 32,
164 11546, 32, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 90434, 519, 0, 1,
165 74433, 32, 85848, 123203, 7305, -900, 1716, 549, 57, 85848, 0, 1, 1, 85848, 123203,
166 7305, -900, 1716, 549, 57, 85848, 0, 1, 955506, 213312, 0, 2, 270652, 22588, 4,
167 1457325, 64566, 4, 20467, 1, 4, 0, 141992, 32, 100788, 420, 1, 1, 81663, 32, 59498, 32,
168 20142, 32, 24588, 32, 20744, 32, 25933, 32, 24623, 32, 43053543, 10, 53384111, 14333,
169 10, 43574283, 26308, 10, 16000, 100, 16000, 100, 962335, 18, 2780678, 6, 442008, 1,
170 52538055, 3756, 18, 267929, 18, 76433006, 8868, 18, 52948122, 18, 1995836, 36, 3227919,
171 12, 901022, 1, 166917843, 4307, 36, 284546, 36, 158221314, 26549, 36, 74698472, 36,
172 333849714, 1, 254006273, 72, 2174038, 72, 2261318, 64571, 4, 207616, 8310, 4, 1293828,
173 28716, 63, 0, 1, 1006041, 43623, 251, 0, 1, 100181, 726, 719, 0, 1, 100181, 726, 719,
174 0, 1, 100181, 726, 719, 0, 1, 107878, 680, 0, 1, 95336, 1, 281145, 18848, 0, 1, 180194,
175 159, 1, 1, 158519, 8942, 0, 1, 159378, 8813, 0, 1, 107490, 3298, 1, 106057, 655, 1,
176 1964219, 24520, 3,
177 ]
178 });
179
180 static TEST_VECTORS: LazyLock<Vec<(Vec<u8>, Option<LanguageViews>)>> = LazyLock::new(|| {
181 vec![
182 (
183 hex::decode(include_str!("../../../test_data/conway1.tx")).unwrap(),
184 Some(LanguageViews::from_iter([(
185 1,
186 COST_MODEL_PLUTUS_V2.clone(),
187 )])),
188 ),
189 (
190 hex::decode(include_str!("../../../test_data/conway2.tx")).unwrap(),
191 Some(LanguageViews::from_iter([(
192 0,
193 COST_MODEL_PLUTUS_V1.clone(),
194 )])),
195 ),
196 (
197 hex::decode(include_str!("../../../test_data/hydra-init.tx")).unwrap(),
198 Some(LanguageViews::from_iter([(
199 1,
200 COST_MODEL_PLUTUS_V2.clone(),
201 )])),
202 ),
203 (
204 hex::decode(include_str!("../../../test_data/datum-only.tx")).unwrap(),
205 None,
206 ),
207 (
208 hex::decode(include_str!("../../../test_data/conway9.tx")).unwrap(),
209 Some(LanguageViews::from_iter([
210 (0, COST_MODEL_PLUTUS_V1.clone()),
211 (1, COST_MODEL_PLUTUS_V2.clone()),
212 (2, COST_MODEL_PLUTUS_V3.clone()),
213 ])),
214 ),
215 ]
216 });
217
218 fn assert_script_data_hash_matches(bytes: &[u8], language_views_opt: &Option<LanguageViews>) {
219 let tx: Tx = pallas_codec::minicbor::decode(bytes).unwrap();
220
221 let witness = tx.transaction_witness_set.clone().unwrap();
222
223 let script_data = ScriptData::build_for(&witness, language_views_opt).unwrap();
224
225 let obtained = script_data.hash();
226
227 let expected = tx.transaction_body.script_data_hash.unwrap();
228
229 assert_eq!(obtained, expected);
230 }
231
232 #[test]
233 fn test_script_data_hash() {
234 for (bytes, language_views_opt) in TEST_VECTORS.iter() {
235 assert_script_data_hash_matches(bytes, language_views_opt);
236 }
237 }
238}