whisky_pallas/wrapper/transaction_body/
transaction_body.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3
4use pallas::codec::utils::{Bytes, NonEmptySet, NonZeroInt, Set};
5use pallas::ledger::primitives::conway::{
6    Certificate as PallasCertificate, GovActionId as PallasGovActionId,
7    Multiasset as PallasMultiasset, NetworkId as PallasNetworkId, PositiveCoin,
8    ProposalProcedure as PallasProposalProcedure, TransactionBody as PallasTransactionBody,
9    TransactionInput as PallasTransactionInput, TransactionOutput as PallasTransactionOutput,
10    Voter as PallasVoter, VotingProcedure as PallasVotingProcedure,
11    VotingProcedures as PallasVotingProcedures,
12};
13use pallas::ledger::primitives::{Coin, Fragment};
14use whisky_common::WError;
15
16use crate::wrapper::transaction_body::{
17    Certificate, GovActionId, MultiassetNonZeroInt, NetworkId, ProposalProcedure, RequiredSigners,
18    RewardAccount, TransactionInput, TransactionOutput, Voter, VotingProdecedure,
19};
20use pallas::crypto::hash::Hash;
21
22#[derive(Debug, PartialEq, Clone)]
23pub struct TransactionBody<'a> {
24    pub inner: PallasTransactionBody<'a>,
25}
26
27impl<'a> TransactionBody<'a> {
28    pub fn new(
29        inputs: Vec<TransactionInput>,
30        outputs: Vec<TransactionOutput<'a>>,
31        fee: u64,
32        ttl: Option<u64>,
33        certificates: Option<Vec<Certificate>>,
34        withdrawals: Option<Vec<(RewardAccount, u64)>>,
35        auxiliary_data_hash: Option<String>,
36        validity_interval_start: Option<u64>,
37        mint: Option<MultiassetNonZeroInt>,
38        script_data_hash: Option<String>,
39        collateral: Option<Vec<TransactionInput>>,
40        required_signers: Option<RequiredSigners>,
41        network_id: Option<NetworkId>,
42        collateral_return: Option<TransactionOutput<'a>>,
43        total_collateral: Option<u64>,
44        reference_inputs: Option<Vec<TransactionInput>>,
45        voting_procedures: Option<Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)>>,
46        proposal_procedures: Option<Vec<ProposalProcedure>>,
47        treasury_value: Option<u64>,
48        donation: Option<u64>,
49    ) -> Result<Self, WError> {
50        let donation_coin = if let Some(d) = donation {
51            Some(PositiveCoin::try_from(d).map_err(|_| {
52                WError::new(
53                    "WhiskyPallas - Creating transaction body:",
54                    "Invalid donation value",
55                )
56            })?)
57        } else {
58            None
59        };
60
61        Ok(Self {
62            inner: PallasTransactionBody {
63                inputs: Self::parse_inputs(inputs),
64                outputs: Self::parse_outputs(outputs),
65                fee: fee,
66                ttl: ttl,
67                certificates: Self::parse_certificates(certificates),
68                withdrawals: Self::parse_withdrawals(withdrawals),
69                auxiliary_data_hash: Self::parse_auxiliary_data_hash(auxiliary_data_hash)?,
70                validity_interval_start: validity_interval_start,
71                mint: Self::parse_mint(mint),
72                script_data_hash: Self::parse_script_data_hash(script_data_hash)?,
73                collateral: Self::parse_collateral(collateral),
74                required_signers: Self::parse_required_signers(required_signers),
75                network_id: Self::parse_network_id(network_id),
76                collateral_return: Self::parse_collateral_return(collateral_return),
77                total_collateral: total_collateral,
78                reference_inputs: Self::parse_reference_inputs(reference_inputs),
79                voting_procedures: Self::parse_voting_procedures(voting_procedures),
80                proposal_procedures: Self::parse_proposal_procedures(proposal_procedures),
81                treasury_value: treasury_value,
82                donation: donation_coin,
83            },
84        })
85    }
86
87    pub fn empty() -> Self {
88        Self {
89            inner: PallasTransactionBody {
90                inputs: Set::from(vec![]),
91                outputs: vec![],
92                fee: 0,
93                ttl: None,
94                certificates: None,
95                withdrawals: None,
96                auxiliary_data_hash: None,
97                validity_interval_start: None,
98                mint: None,
99                script_data_hash: None,
100                collateral: None,
101                required_signers: None,
102                network_id: None,
103                collateral_return: None,
104                total_collateral: None,
105                reference_inputs: None,
106                voting_procedures: None,
107                proposal_procedures: None,
108                treasury_value: None,
109                donation: None,
110            },
111        }
112    }
113
114    pub fn encode(&self) -> Result<String, WError> {
115        self.inner
116            .encode_fragment()
117            .map(|bytes| hex::encode(bytes))
118            .map_err(|_| {
119                WError::new(
120                    "WhiskyPallas - Encoding transaction body:",
121                    "Failed to encode fragment",
122                )
123            })
124    }
125
126    pub fn decode_bytes(bytes: &'a [u8]) -> Result<Self, WError> {
127        let inner = PallasTransactionBody::decode_fragment(bytes).map_err(|e| {
128            WError::new(
129                "WhiskyPallas - Decoding transaction body:",
130                &format!("Fragment decode error: {}", e.to_string()),
131            )
132        })?;
133        Ok(Self { inner })
134    }
135
136    fn parse_inputs(inputs: Vec<TransactionInput>) -> Set<PallasTransactionInput> {
137        let pallas_inputs: Vec<PallasTransactionInput> =
138            inputs.into_iter().map(|input| input.inner).collect();
139        Set::from(pallas_inputs)
140    }
141
142    fn parse_outputs(outputs: Vec<TransactionOutput<'a>>) -> Vec<PallasTransactionOutput<'a>> {
143        outputs.into_iter().map(|output| output.inner).collect()
144    }
145
146    fn parse_certificates(
147        certificates: Option<Vec<Certificate>>,
148    ) -> Option<NonEmptySet<PallasCertificate>> {
149        match certificates {
150            Some(certs) => {
151                let pallas_certs: Vec<PallasCertificate> =
152                    certs.into_iter().map(|cert| cert.inner).collect();
153                NonEmptySet::from_vec(pallas_certs)
154            }
155            None => None,
156        }
157    }
158
159    fn parse_withdrawals(
160        withdrawals: Option<Vec<(RewardAccount, u64)>>,
161    ) -> Option<BTreeMap<Bytes, Coin>> {
162        withdrawals
163            .map(|wds| BTreeMap::from_iter(wds.into_iter().map(|(ra, coin)| (ra.inner, coin))))
164    }
165
166    fn parse_auxiliary_data_hash(
167        auxiliary_data_hash: Option<String>,
168    ) -> Result<Option<Hash<32>>, WError> {
169        match auxiliary_data_hash {
170            Some(hash_str) => Hash::from_str(&hash_str).map(Some).map_err(|_| {
171                WError::new(
172                    "WhiskyPallas - Parsing auxiliary data hash:",
173                    "Invalid auxiliary hash format",
174                )
175            }),
176            None => Ok(None),
177        }
178    }
179
180    fn parse_mint(mint: Option<MultiassetNonZeroInt>) -> Option<PallasMultiasset<NonZeroInt>> {
181        mint.map(|ma| ma.inner)
182    }
183
184    fn parse_script_data_hash(
185        script_data_hash: Option<String>,
186    ) -> Result<Option<Hash<32>>, WError> {
187        match script_data_hash {
188            Some(hash_str) => Hash::from_str(&hash_str).map(Some).map_err(|_| {
189                WError::new(
190                    "WhiskyPallas - Parsing script data hash:",
191                    "Invalid script data hash format",
192                )
193            }),
194            None => Ok(None),
195        }
196    }
197
198    fn parse_collateral(
199        collateral: Option<Vec<TransactionInput>>,
200    ) -> Option<NonEmptySet<PallasTransactionInput>> {
201        let collatera_vec = collateral.map(|inputs| {
202            let pallas_inputs: Vec<PallasTransactionInput> =
203                inputs.into_iter().map(|input| input.inner).collect();
204            pallas_inputs
205        });
206        match collatera_vec {
207            Some(vec) => NonEmptySet::from_vec(vec),
208            None => None,
209        }
210    }
211
212    fn parse_required_signers(
213        required_signers: Option<RequiredSigners>,
214    ) -> Option<NonEmptySet<Hash<28>>> {
215        required_signers.map(|rs| rs.inner)
216    }
217
218    fn parse_network_id(network_id: Option<NetworkId>) -> Option<PallasNetworkId> {
219        network_id.map(|nid| nid.inner)
220    }
221
222    fn parse_collateral_return(
223        collateral_return: Option<TransactionOutput<'a>>,
224    ) -> Option<PallasTransactionOutput<'a>> {
225        collateral_return.map(|cr| cr.inner)
226    }
227
228    fn parse_reference_inputs(
229        reference_inputs: Option<Vec<TransactionInput>>,
230    ) -> Option<NonEmptySet<PallasTransactionInput>> {
231        let ref_inputs_vec = reference_inputs.map(|inputs| {
232            let pallas_inputs: Vec<PallasTransactionInput> =
233                inputs.into_iter().map(|input| input.inner).collect();
234            pallas_inputs
235        });
236        match ref_inputs_vec {
237            Some(vec) => NonEmptySet::from_vec(vec),
238            None => None,
239        }
240    }
241
242    fn parse_voting_procedures(
243        voting_procedures: Option<Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)>>,
244    ) -> Option<PallasVotingProcedures> {
245        let mut voting_procedures_map: BTreeMap<
246            PallasVoter,
247            BTreeMap<PallasGovActionId, PallasVotingProcedure>,
248        > = BTreeMap::new();
249        match voting_procedures {
250            None => return None,
251            Some(vp) => {
252                for (voter, actions) in vp {
253                    let pallas_voter = voter.inner;
254                    let mut pallas_action_map: BTreeMap<PallasGovActionId, PallasVotingProcedure> =
255                        BTreeMap::new();
256
257                    for (gov_action_id, voting_procedure) in actions {
258                        pallas_action_map.insert(gov_action_id.inner, voting_procedure.inner);
259                    }
260                    voting_procedures_map.insert(pallas_voter, pallas_action_map);
261                }
262                Some(voting_procedures_map)
263            }
264        }
265    }
266
267    fn parse_proposal_procedures(
268        proposal_procedures: Option<Vec<ProposalProcedure>>,
269    ) -> Option<NonEmptySet<PallasProposalProcedure>> {
270        match proposal_procedures {
271            Some(pp) => {
272                let pallas_pp: Vec<PallasProposalProcedure> =
273                    pp.into_iter().map(|proc| proc.inner).collect();
274                NonEmptySet::from_vec(pallas_pp)
275            }
276            None => None,
277        }
278    }
279}