whisky_pallas/wrapper/transaction_body/
transaction_body.rs1use 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}