seedelf_core/
withdraw_zero.rs1use anyhow::{Context, Result, anyhow, bail};
14use pallas_codec::minicbor;
15use pallas_codec::utils::{Bytes, MaybeIndefArray};
16use pallas_crypto::hash::{Hash, Hasher};
17use pallas_primitives::conway::{self, ExUnits, PlutusData, Redeemer, RedeemerTag, Redeemers};
18use pallas_primitives::{Fragment, NonEmptyKeyValuePairs};
19use pallas_txbuilder::BuiltTransaction;
20
21use crate::build::tx_id;
22
23pub fn reward_account(script_hash: &[u8; 28], network_flag: bool) -> Vec<u8> {
26 let mut account = Vec::with_capacity(29);
27 account.push(if network_flag { 0xf0 } else { 0xf1 });
28 account.extend_from_slice(script_hash);
29 account
30}
31
32pub fn script_data_hash(
37 redeemers_cbor: &[u8],
38 datums_cbor: Option<&[u8]>,
39 cost_model_v3: &[i64],
40) -> Result<Hash<32>> {
41 let mut preimage = redeemers_cbor.to_vec();
42 if let Some(datums) = datums_cbor {
43 preimage.extend_from_slice(datums);
44 }
45 let mut view = minicbor::Encoder::new(Vec::new());
46 view.map(1)
47 .and_then(|e| e.u8(2))
48 .and_then(|e| e.encode(cost_model_v3))
49 .map_err(|e| anyhow!("The language view can't be encoded: {e}"))?;
50 preimage.extend(view.into_writer());
51 Ok(Hasher::<256>::hash(&preimage))
52}
53
54#[derive(Debug, Clone)]
56pub struct WithdrawZero {
57 pub account: Vec<u8>,
58 pub redeemer: PlutusData,
59}
60
61impl WithdrawZero {
62 pub fn new(script_hash: &[u8; 28], network_flag: bool, redeemer_cbor: &[u8]) -> Result<Self> {
63 let redeemer = PlutusData::decode_fragment(redeemer_cbor)
64 .map_err(|e| anyhow!("The withdrawal's redeemer isn't Plutus data: {e}"))?;
65 Ok(WithdrawZero {
66 account: reward_account(script_hash, network_flag),
67 redeemer,
68 })
69 }
70
71 pub fn patch(
76 &self,
77 mut built: BuiltTransaction,
78 budget: ExUnits,
79 cost_model_v3: &[i64],
80 ) -> Result<BuiltTransaction> {
81 if built.signatures.is_some() {
82 bail!("A transaction is patched before it's signed, not after");
83 }
84 let mut tx = conway::Tx::decode_fragment(&built.tx_bytes.0)
85 .map_err(|e| anyhow!("The built transaction isn't a Conway transaction: {e}"))?;
86 let body = &mut tx.transaction_body;
87 let account = Bytes::from(self.account.clone());
88 let others = body
89 .withdrawals
90 .as_ref()
91 .map(|w| w.iter().any(|(a, _)| *a != account))
92 .unwrap_or(false);
93 if others {
94 bail!("The transaction already withdraws from another account");
95 }
96 body.withdrawals = Some(NonEmptyKeyValuePairs::Def(vec![(account, 0)]));
97
98 let witness = &mut tx.transaction_witness_set;
99 if witness.plutus_data.is_some() {
100 bail!("A transaction with witness datums can't be given a withdraw-zero yet");
101 }
102 let mut redeemers = match witness.redeemer.take() {
103 None => Vec::new(),
104 Some(Redeemers::List(list)) => list.to_vec(),
105 Some(Redeemers::Map(_)) => {
106 bail!("The transaction's redeemers are a map, which this patch doesn't write")
107 }
108 };
109 redeemers.retain(|r| r.tag != RedeemerTag::Reward);
111 redeemers.push(Redeemer {
112 tag: RedeemerTag::Reward,
113 index: 0,
114 data: self.redeemer.clone(),
115 ex_units: budget,
116 });
117 let redeemers = Redeemers::List(MaybeIndefArray::Def(redeemers));
118 let redeemers_cbor = minicbor::to_vec(&redeemers)
119 .map_err(|e| anyhow!("The redeemers can't be encoded: {e}"))?;
120 tx.transaction_body.script_data_hash =
121 Some(script_data_hash(&redeemers_cbor, None, cost_model_v3)?);
122 tx.transaction_witness_set.redeemer = Some(redeemers);
123
124 let bytes = tx
125 .encode_fragment()
126 .map_err(|e| anyhow!("Failed to encode the transaction: {e}"))?;
127 built.tx_hash.0 = *tx_id(&bytes).context("The patched transaction can't be hashed")?;
128 built.tx_bytes.0 = bytes;
129 Ok(built)
130 }
131}