1use super::{
2 certificate_builder::CertificateBuilderResult, input_builder::InputBuilderResult,
3 mint_builder::MintBuilderResult, proposal_builder::ProposalBuilderResult,
4 vote_builder::VoteBuilderResult, withdrawal_builder::WithdrawalBuilderResult,
5};
6use crate::{
7 address::RewardAddress,
8 plutus::{ExUnits, LegacyRedeemer, PlutusData, RedeemerTag, Redeemers},
9 transaction::TransactionInput,
10 PolicyId,
11};
12use std::{collections::BTreeMap, fmt::Debug};
13
14#[derive(Clone, Copy, PartialOrd, Ord, Debug, PartialEq, Eq, Hash)]
15pub struct RedeemerWitnessKey {
16 tag: RedeemerTag,
17 index: u64,
18}
19
20impl RedeemerWitnessKey {
21 pub fn new(tag: RedeemerTag, index: u64) -> Self {
22 Self { tag, index }
23 }
24}
25
26impl From<&LegacyRedeemer> for RedeemerWitnessKey {
27 fn from(redeemer: &LegacyRedeemer) -> Self {
28 Self {
29 tag: redeemer.tag,
30 index: redeemer.index,
31 }
32 }
33}
34
35#[derive(Clone, Debug)]
39pub struct UntaggedRedeemer {
40 pub data: PlutusData,
41 pub ex_units: ExUnits,
42}
43
44impl UntaggedRedeemer {
45 pub fn new(data: PlutusData, ex_units: ExUnits) -> Self {
46 Self { data, ex_units }
47 }
48}
49
50#[derive(Clone, Debug)]
51enum UntaggedRedeemerPlaceholder {
52 JustData(PlutusData),
53 Full(UntaggedRedeemer),
54}
55
56impl UntaggedRedeemerPlaceholder {
57 fn data(&self) -> &PlutusData {
58 match self {
59 Self::JustData(data) => data,
60 Self::Full(untagged_redeemer) => &untagged_redeemer.data,
61 }
62 }
63}
64
65#[derive(Debug, thiserror::Error)]
67pub enum MissingExunitError {
68 #[error("Missing exunit for {0:?} with <key, index> values of <{1:?}, {2}>")]
69 Key(RedeemerTag, usize, String),
70}
71
72#[derive(Debug, thiserror::Error)]
73pub enum RedeemerBuilderError {
74 #[error("Missing ExUnit: {0}")]
75 MissingExUnit(#[from] MissingExunitError),
76}
77
78#[derive(Clone, Default, Debug)]
81pub struct RedeemerSetBuilder {
82 spend: BTreeMap<TransactionInput, Option<UntaggedRedeemerPlaceholder>>,
86
87 mint: BTreeMap<PolicyId, Option<UntaggedRedeemerPlaceholder>>,
92
93 reward: BTreeMap<RewardAddress, Option<UntaggedRedeemerPlaceholder>>,
97
98 cert: Vec<Option<UntaggedRedeemerPlaceholder>>,
101
102 proposals: Vec<Option<UntaggedRedeemerPlaceholder>>,
103
104 votes: Vec<Option<UntaggedRedeemerPlaceholder>>,
105}
106
107impl RedeemerSetBuilder {
108 pub fn new() -> Self {
109 Self::default()
110 }
111
112 pub fn is_empty(&self) -> bool {
113 self.spend.is_empty()
114 && self.mint.is_empty()
115 && self.reward.is_empty()
116 && self.cert.is_empty()
117 }
118
119 pub fn update_ex_units(&mut self, key: RedeemerWitnessKey, ex_units: ExUnits) {
121 match key.tag {
122 RedeemerTag::Spend => {
123 let entry = self.spend.iter_mut().nth(key.index as usize).unwrap().1;
124 *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new(
125 entry.as_ref().unwrap().data().clone(),
126 ex_units,
127 )));
128 }
129 RedeemerTag::Mint => {
130 let entry = self.mint.iter_mut().nth(key.index as usize).unwrap().1;
131 *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new(
132 entry.as_ref().unwrap().data().clone(),
133 ex_units,
134 )));
135 }
136 RedeemerTag::Cert => {
137 let entry = self.cert.get_mut(key.index as usize).unwrap();
138 *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new(
139 entry.as_ref().unwrap().data().clone(),
140 ex_units,
141 )));
142 }
143 RedeemerTag::Reward => {
144 let entry = self.reward.iter_mut().nth(key.index as usize).unwrap().1;
145 *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new(
146 entry.as_ref().unwrap().data().clone(),
147 ex_units,
148 )));
149 }
150 RedeemerTag::Proposing => {
151 let entry = self.proposals.get_mut(key.index as usize).unwrap();
152 *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new(
153 entry.as_ref().unwrap().data().clone(),
154 ex_units,
155 )));
156 }
157 RedeemerTag::Voting => {
158 let entry = self.votes.get_mut(key.index as usize).unwrap();
159 *entry = Some(UntaggedRedeemerPlaceholder::Full(UntaggedRedeemer::new(
160 entry.as_ref().unwrap().data().clone(),
161 ex_units,
162 )));
163 }
164 }
165 }
166
167 pub fn add_spend(&mut self, result: &InputBuilderResult) {
168 let redeemer_data = {
169 result
170 .aggregate_witness
171 .as_ref()
172 .and_then(|data| data.redeemer_plutus_data())
173 };
174 if let Some(data) = redeemer_data {
175 self.spend.insert(
176 result.input.clone(),
177 Some(UntaggedRedeemerPlaceholder::JustData(data.clone())),
178 );
179 } else {
180 self.spend.insert(result.input.clone(), None);
181 }
182 }
183
184 pub fn add_mint(&mut self, result: &MintBuilderResult) {
185 let redeemer_data = {
186 result
187 .aggregate_witness
188 .as_ref()
189 .and_then(|data| data.redeemer_plutus_data())
190 };
191 if let Some(data) = redeemer_data {
192 self.mint.insert(
193 result.policy_id,
194 Some(UntaggedRedeemerPlaceholder::JustData(data.clone())),
195 );
196 } else {
197 self.mint.insert(result.policy_id, None);
198 }
199 }
200
201 pub fn add_reward(&mut self, result: &WithdrawalBuilderResult) {
202 let redeemer_data = {
203 result
204 .aggregate_witness
205 .as_ref()
206 .and_then(|data| data.redeemer_plutus_data())
207 };
208 if let Some(data) = redeemer_data {
209 self.reward.insert(
210 result.address.clone(),
211 Some(UntaggedRedeemerPlaceholder::JustData(data.clone())),
212 );
213 } else {
214 self.reward.insert(result.address.clone(), None);
215 }
216 }
217
218 pub fn add_cert(&mut self, result: &CertificateBuilderResult) {
219 let redeemer_data = {
220 result
221 .aggregate_witness
222 .as_ref()
223 .and_then(|data| data.redeemer_plutus_data())
224 };
225 if let Some(data) = redeemer_data {
226 self.cert
227 .push(Some(UntaggedRedeemerPlaceholder::JustData(data.clone())));
228 } else {
229 self.cert.push(None);
230 }
231 }
232
233 pub fn add_proposal(&mut self, result: &ProposalBuilderResult) {
234 for aggregate_witness in &result.aggregate_witnesses {
235 if let Some(data) = aggregate_witness.redeemer_plutus_data() {
236 self.proposals
237 .push(Some(UntaggedRedeemerPlaceholder::JustData(data.clone())));
238 } else {
239 self.proposals.push(None);
240 }
241 }
242 }
243
244 pub fn add_vote(&mut self, result: &VoteBuilderResult) {
245 for aggregate_witness in &result.aggregate_witnesses {
246 if let Some(data) = aggregate_witness.redeemer_plutus_data() {
247 self.votes
248 .push(Some(UntaggedRedeemerPlaceholder::JustData(data.clone())));
249 } else {
250 self.votes.push(None);
251 }
252 }
253 }
254
255 pub fn build(&self, default_to_dummy_exunits: bool) -> Result<Redeemers, RedeemerBuilderError> {
256 let mut redeemers = Vec::new();
257 self.remove_placeholders_and_tag(
259 &mut redeemers,
260 RedeemerTag::Spend,
261 &mut self.spend.iter(),
262 default_to_dummy_exunits,
263 )?;
264 self.remove_placeholders_and_tag(
265 &mut redeemers,
266 RedeemerTag::Mint,
267 &mut self.mint.iter(),
268 default_to_dummy_exunits,
269 )?;
270 self.remove_placeholders_and_tag(
271 &mut redeemers,
272 RedeemerTag::Reward,
273 &mut self.reward.iter(),
274 default_to_dummy_exunits,
275 )?;
276 self.remove_placeholders_and_tag(
277 &mut redeemers,
278 RedeemerTag::Cert,
279 &mut self.cert.iter().map(|entry| (&(), entry)),
280 default_to_dummy_exunits,
281 )?;
282 self.remove_placeholders_and_tag(
283 &mut redeemers,
284 RedeemerTag::Proposing,
285 &mut self.proposals.iter().map(|entry| (&(), entry)),
286 default_to_dummy_exunits,
287 )?;
288 self.remove_placeholders_and_tag(
289 &mut redeemers,
290 RedeemerTag::Voting,
291 &mut self.votes.iter().map(|entry| (&(), entry)),
292 default_to_dummy_exunits,
293 )?;
294
295 Ok(Redeemers::new_arr_legacy_redeemer(redeemers))
296 }
297
298 fn remove_placeholders_and_tag<'a, K: Debug + Clone>(
299 &self,
300 redeemers: &mut Vec<LegacyRedeemer>,
301 tag: RedeemerTag,
302 entries: &mut dyn Iterator<Item = (&'a K, &'a Option<UntaggedRedeemerPlaceholder>)>,
303 default_to_dummy_exunits: bool,
304 ) -> Result<(), RedeemerBuilderError> {
305 let mut result = vec![];
306 for (i, entry) in entries.enumerate() {
307 let key = (tag, i, entry.0);
308
309 let redeemer = match entry.1 {
310 Some(UntaggedRedeemerPlaceholder::JustData(data)) => {
311 if !default_to_dummy_exunits {
312 Err(RedeemerBuilderError::MissingExUnit(
313 MissingExunitError::Key(key.0, key.1, format!("{:?}", key.2)),
314 ))
315 } else {
316 Ok(Some(UntaggedRedeemer::new(data.clone(), ExUnits::dummy())))
317 }
318 }
319 Some(UntaggedRedeemerPlaceholder::Full(untagged_redeemer)) => {
320 Ok(Some(untagged_redeemer.clone()))
321 }
322 None => Ok(None),
323 }?;
324 result.push(redeemer);
325 }
326 redeemers.append(&mut Self::tag_redeemer(tag, &result));
327 Ok(())
328 }
329
330 fn tag_redeemer(
331 tag: RedeemerTag,
332 untagged_redeemers: &[Option<UntaggedRedeemer>],
333 ) -> Vec<LegacyRedeemer> {
334 let mut result = Vec::new();
335
336 for (index, untagged_redeemer) in untagged_redeemers.iter().enumerate() {
337 if let Some(untagged_redeemer) = untagged_redeemer {
338 result.push(LegacyRedeemer::new(
339 tag,
340 index as u64,
341 untagged_redeemer.data.clone(),
342 untagged_redeemer.ex_units.clone(),
343 ));
344 }
345 }
346 result
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use crate::{
353 address::Address,
354 builders::witness_builder::{
355 InputAggregateWitnessData, PartialPlutusWitness, PlutusScriptWitness,
356 RequiredWitnessSet,
357 },
358 plutus::{PlutusScript, PlutusV1Script},
359 transaction::AlonzoFormatTxOut,
360 Value,
361 };
362 use cml_crypto::{PublicKey, RawBytesEncoding, TransactionHash};
363
364 use super::*;
365
366 fn fake_raw_key_public(id: u8) -> PublicKey {
367 PublicKey::from_raw_bytes(&[
368 id, 118, 57, 154, 33, 13, 232, 114, 14, 159, 168, 148, 228, 94, 65, 226, 154, 181, 37,
369 227, 11, 196, 2, 128, 28, 7, 98, 80, 209, 88, 91, 205,
370 ])
371 .unwrap()
372 }
373
374 #[test]
375 fn test_redeemer_set_builder() {
376 let mut builder = RedeemerSetBuilder::new();
377
378 let data = {
379 let witness = {
380 let script = PlutusScript::PlutusV1(PlutusV1Script::new(vec![0]));
381 PartialPlutusWitness {
382 script: PlutusScriptWitness::Script(script),
383 redeemer: PlutusData::new_integer(0u64.into()),
384 }
385 };
386 let missing_signers = vec![fake_raw_key_public(0).hash()];
387 InputAggregateWitnessData::PlutusScript(witness, missing_signers.into(), None)
388 };
389
390 let address = Address::from_bech32("addr1qxeqxcja25k8q05evyngf4f88xn89asl54x2zg3ephgj26ndyt5qk02xmmras5pe9jz2c7tc93wu4c96rqwvg6e2v50qlpmx70").unwrap();
391
392 let input_result = InputBuilderResult {
393 input: TransactionInput::new(TransactionHash::from([1; 32]), 1),
394 utxo_info: AlonzoFormatTxOut::new(address.clone(), Value::zero()).into(),
395 aggregate_witness: None,
396 required_wits: RequiredWitnessSet::new(),
397 };
398
399 builder.add_spend(&input_result);
400
401 let input_result = InputBuilderResult {
402 input: TransactionInput::new(TransactionHash::from([1; 32]), 0),
403 utxo_info: AlonzoFormatTxOut::new(address.clone(), Value::zero()).into(),
404 aggregate_witness: None,
405 required_wits: RequiredWitnessSet::new(),
406 };
407
408 builder.add_spend(&input_result);
409
410 let input_result = InputBuilderResult {
411 input: TransactionInput::new(TransactionHash::from([0; 32]), 0),
412 utxo_info: AlonzoFormatTxOut::new(address, Value::zero()).into(),
413 aggregate_witness: Some(data),
414 required_wits: RequiredWitnessSet::new(),
415 };
416
417 builder.add_spend(&input_result);
418
419 builder.update_ex_units(
420 RedeemerWitnessKey::new(RedeemerTag::Spend, 0),
421 ExUnits::new(10, 10),
422 );
423
424 let redeemers = builder.build(false).unwrap().to_flat_format();
425
426 assert_eq!(redeemers.len(), 1);
427
428 let spend_redeemer = &redeemers[0];
429
430 assert_eq!(spend_redeemer.tag, RedeemerTag::Spend);
431 assert_eq!(spend_redeemer.index, 0);
432 }
433}