1use std::collections::HashMap;
2
3use pallas::codec::utils::PositiveCoin;
4use pallas::ledger::primitives::conway::{
5 LanguageView, Redeemer as PallasRedeemer, RedeemerTag as PallasRedeemerTag, ScriptData,
6 Value as PallasValue, WitnessSet as PallasWitnessSet,
7};
8use pallas::ledger::primitives::Fragment;
9use whisky_common::{get_cost_models_from_network, MintItem, Output, PubKeyTxIn, RefTxIn};
10use whisky_common::{
11 Certificate as WhiskyCertificate,
12 Certificate::{BasicCertificate, ScriptCertificate, SimpleScriptCertificate},
13 CertificateType,
14 DatumSource::{self, InlineDatumSource, ProvidedDatumSource},
15 LanguageVersion,
16 ScriptSource::{self, InlineScriptSource, ProvidedScriptSource},
17 SimpleScriptTxInParameter::{InlineSimpleScriptSource, ProvidedSimpleScriptSource},
18 TxBuilderBody, TxIn, Vote as WhiskyVote, WError, Withdrawal as WhiskyWithdrawal,
19 Withdrawal::{PlutusScriptWithdrawal, PubKeyWithdrawal, SimpleScriptWithdrawal},
20};
21
22use crate::utils::{calculate_fee, required_signatures_to_mock_witnesses};
23use crate::{
24 converter::{bytes_from_bech32, convert_value},
25 wrapper::{
26 transaction_body::{
27 Anchor, Certificate, CertificateKind, DRep, DRepKind, Datum, DatumKind, GovActionId,
28 MultiassetNonZeroInt, MultiassetPositiveCoin, NetworkId, NetworkIdKind, PoolMetadata,
29 Relay, RelayKind, RequiredSigners, RewardAccount, ScriptRef, ScriptRefKind,
30 StakeCredential, StakeCredentialKind, Transaction, TransactionBody, TransactionInput,
31 TransactionOutput, Value, Vote, VoteKind, Voter, VoterKind, VotingProdecedure,
32 },
33 witness_set::{
34 native_script::NativeScript,
35 plutus_data::PlutusData,
36 plutus_script::PlutusScript,
37 redeemer::{ExUnits, Redeemer, RedeemerTag},
38 witness_set::WitnessSet,
39 },
40 },
41};
42
43#[derive(Clone, Debug)]
44pub struct CorePallas {
45 pub protocol_params: whisky_common::Protocol,
46
47 pub inputs_map: HashMap<TransactionInput, Value>,
49 pub collaterals_map: HashMap<TransactionInput, Value>,
50 pub script_source_ref_inputs: Vec<RefTxIn>,
51 pub total_script_size: usize,
52 pub required_signatures_vec: Vec<String>,
53
54 pub native_scripts_vec: Vec<NativeScript>,
56 pub plutus_v1_scripts_vec: Vec<PlutusScript<1>>,
57 pub plutus_v2_scripts_vec: Vec<PlutusScript<2>>,
58 pub plutus_v3_scripts_vec: Vec<PlutusScript<3>>,
59 pub plutus_v1_used: bool,
60 pub plutus_v2_used: bool,
61 pub plutus_v3_used: bool,
62 pub input_redeemers_vec: Vec<(TransactionInput, Redeemer)>,
63 pub certificate_redeemers_vec: Vec<(Certificate, Redeemer)>,
64 pub withdrawal_redeemers_vec: Vec<(RewardAccount, Redeemer)>,
65 pub mint_redeemers_vec: Vec<(String, Redeemer)>,
66 pub vote_redeemers_vec: Vec<(Voter, Redeemer)>,
67 pub plutus_data_vec: Vec<PlutusData>,
68
69 pub ref_inputs_vec: Vec<TransactionInput>,
71}
72
73impl CorePallas {
74 pub fn new(protocol_params: whisky_common::Protocol) -> Self {
75 Self {
76 protocol_params,
77 inputs_map: HashMap::new(),
78 collaterals_map: HashMap::new(),
79 script_source_ref_inputs: vec![],
80 total_script_size: 0,
81 required_signatures_vec: vec![],
82 native_scripts_vec: vec![],
83 plutus_v1_scripts_vec: vec![],
84 plutus_v2_scripts_vec: vec![],
85 plutus_v3_scripts_vec: vec![],
86 plutus_v1_used: false,
87 plutus_v2_used: false,
88 plutus_v3_used: false,
89 input_redeemers_vec: vec![],
90 certificate_redeemers_vec: vec![],
91 withdrawal_redeemers_vec: vec![],
92 mint_redeemers_vec: vec![],
93 vote_redeemers_vec: vec![],
94 plutus_data_vec: vec![],
95 ref_inputs_vec: vec![],
96 }
97 }
98
99 fn process_inputs(
100 &mut self,
101 whisky_inputs: Vec<TxIn>,
102 ) -> Result<Vec<TransactionInput>, WError> {
103 let mut inputs: Vec<TransactionInput> = vec![];
104 for tx_in in whisky_inputs.clone() {
105 match tx_in {
106 TxIn::PubKeyTxIn(pub_key_tx_in) => {
107 let input = TransactionInput::new(
108 &pub_key_tx_in.tx_in.tx_hash,
109 pub_key_tx_in.tx_in.tx_index.into(),
110 )?;
111 let asset_vec = pub_key_tx_in.tx_in.amount.clone().ok_or_else(|| {
112 WError::new("WhiskyPallas - Adding inputs:", "Input amount is missing")
113 })?;
114 let value = convert_value(&asset_vec)?;
115 self.inputs_map.insert(input.clone(), value);
116 self.required_signatures_vec
117 .push(bytes_from_bech32(&pub_key_tx_in.tx_in.address.unwrap())?);
118 inputs.push(input);
119 }
120 TxIn::SimpleScriptTxIn(simple_script_tx_in) => {
121 let input = TransactionInput::new(
122 &simple_script_tx_in.tx_in.tx_hash,
123 simple_script_tx_in.tx_in.tx_index.into(),
124 )?;
125 let asset_vec = simple_script_tx_in.tx_in.amount.clone().ok_or_else(|| {
126 WError::new("WhiskyPallas - Adding inputs:", "Input amount is missing")
127 })?;
128 let value = convert_value(&asset_vec)?;
129 self.inputs_map.insert(input.clone(), value);
130 inputs.push(input);
131
132 match &simple_script_tx_in.simple_script_tx_in {
133 ProvidedSimpleScriptSource(provided_simple_script_source) => {
134 self.native_scripts_vec.push(NativeScript::new_from_hex(
135 &provided_simple_script_source.script_cbor.clone(),
136 )?);
137 }
138 InlineSimpleScriptSource(inline_simple_script_source) => {
139 self.ref_inputs_vec.push(TransactionInput::new(
140 &inline_simple_script_source.ref_tx_in.tx_hash,
141 inline_simple_script_source.ref_tx_in.tx_index.into(),
142 )?)
143 }
144 }
145 }
146 TxIn::ScriptTxIn(script_tx_in) => {
147 let input = TransactionInput::new(
148 &script_tx_in.tx_in.tx_hash,
149 script_tx_in.tx_in.tx_index.into(),
150 )?;
151 let asset_vec = script_tx_in.tx_in.amount.clone().ok_or_else(|| {
152 WError::new("WhiskyPallas - Adding inputs:", "Input amount is missing")
153 })?;
154 let value = convert_value(&asset_vec)?;
155 self.inputs_map.insert(input.clone(), value);
156 inputs.push(input.clone());
157
158 let script_source = script_tx_in
159 .script_tx_in
160 .script_source
161 .clone()
162 .ok_or_else(|| {
163 WError::new(
164 "WhiskyPallas - Adding inputs",
165 "Script source is missing from script input",
166 )
167 })?;
168
169 let datum_source =
170 script_tx_in
171 .script_tx_in
172 .datum_source
173 .clone()
174 .ok_or_else(|| {
175 WError::new(
176 "WhiskyPallas - Adding inputs",
177 "Datum source is missing from script input",
178 )
179 })?;
180
181 let redeemer = script_tx_in.script_tx_in.redeemer.clone().ok_or_else(|| {
182 WError::new(
183 "WhiskyPallas - Adding inputs",
184 "Redeemer is missing from script input",
185 )
186 })?;
187
188 self.process_script_source(script_source)?;
189
190 self.process_datum_source(datum_source)?;
191
192 self.input_redeemers_vec.push((
193 input.clone(),
194 Redeemer::new(
195 RedeemerTag::Spend,
196 0,
197 PlutusData::new(redeemer.data)?,
198 ExUnits {
199 mem: redeemer.ex_units.mem,
200 steps: redeemer.ex_units.steps,
201 },
202 )?,
203 ));
204 }
205 }
206 }
207 inputs.sort_by(|a, b| {
208 a.inner
209 .transaction_id
210 .cmp(&b.inner.transaction_id)
211 .then(a.inner.index.cmp(&b.inner.index))
212 });
213 Ok(inputs)
214 }
215
216 fn process_outputs(
217 &mut self,
218 whisky_outputs: Vec<Output>,
219 ) -> Result<Vec<TransactionOutput<'static>>, WError> {
220 let mut outputs: Vec<TransactionOutput> = vec![];
221 let whisky_outputs = whisky_outputs.clone();
222 for output in &whisky_outputs {
223 let datum: Option<Datum> = match &output.datum {
224 Some(datum_source) => match datum_source {
225 whisky_common::Datum::Inline(datum_str) => Some(Datum::new(DatumKind::Data {
226 plutus_data_hex: datum_str.to_string(),
227 })?),
228 whisky_common::Datum::Hash(datum_str) => {
229 let datum = Datum::new(DatumKind::Data {
230 plutus_data_hex: datum_str.to_string(),
231 })?;
232
233 let datum_hash_str = datum.hash()?;
234 Some(Datum::new(DatumKind::Hash {
235 datum_hash: datum_hash_str,
236 })?)
237 }
238 whisky_common::Datum::Embedded(datum_str) => {
239 let datum = Datum::new(DatumKind::Data {
240 plutus_data_hex: datum_str.to_string(),
241 })?;
242 self.plutus_data_vec
243 .push(PlutusData::new(datum_str.to_string())?);
244
245 let datum_hash_str = datum.hash()?;
246 Some(Datum::new(DatumKind::Hash {
247 datum_hash: datum_hash_str,
248 })?)
249 }
250 },
251 None => None,
252 };
253
254 let script_ref = match &output.reference_script {
255 Some(script_source) => match script_source {
256 whisky_common::OutputScriptSource::ProvidedScriptSource(
257 provided_script_source,
258 ) => {
259 let plutus_script = match provided_script_source.language_version {
260 LanguageVersion::V1 => ScriptRef::new(ScriptRefKind::PlutusV1Script {
261 plutus_v1_script_hex: provided_script_source.script_cbor.clone(),
262 })?,
263 LanguageVersion::V2 => ScriptRef::new(ScriptRefKind::PlutusV2Script {
264 plutus_v2_script_hex: provided_script_source.script_cbor.clone(),
265 })?,
266 LanguageVersion::V3 => ScriptRef::new(ScriptRefKind::PlutusV3Script {
267 plutus_v3_script_hex: provided_script_source.script_cbor.clone(),
268 })?,
269 };
270 Some(plutus_script)
271 }
272 whisky_common::OutputScriptSource::ProvidedSimpleScriptSource(
273 provided_simple_script_source,
274 ) => {
275 let native_script = ScriptRef::new(ScriptRefKind::NativeScript {
276 native_script_hex: provided_simple_script_source.script_cbor.clone(),
277 })?;
278 Some(native_script)
279 }
280 },
281 None => None,
282 };
283 outputs.push(TransactionOutput::new(
284 &bytes_from_bech32(&output.address)?,
285 convert_value(&output.amount.clone())?,
286 datum,
287 script_ref,
288 )?);
289 }
290 Ok(outputs)
291 }
292
293 fn process_certificate_type(
294 &mut self,
295 cert_type: &CertificateType,
296 ) -> Result<Certificate, WError> {
297 match cert_type {
298 CertificateType::RegisterStake(register_stake) => {
299 let stake_cred = RewardAccount::from_bech32(®ister_stake.stake_key_address)?
300 .to_stake_cred()?;
301 match stake_cred.inner {
302 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
303 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
304 self.required_signatures_vec.push(hash.to_string());
305 }
306 }
307 Ok(Certificate::new(CertificateKind::StakeRegistration {
308 stake_credential: stake_cred,
309 }))?
310 }
311 CertificateType::DeregisterStake(deregister_stake) => {
312 let stake_cred = RewardAccount::from_bech32(&deregister_stake.stake_key_address)?
313 .to_stake_cred()?;
314 match stake_cred.inner {
315 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
316 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
317 self.required_signatures_vec.push(hash.to_string());
318 }
319 }
320 Ok(Certificate::new(CertificateKind::StakeDeregistration {
321 stake_credential: stake_cred,
322 }))?
323 }
324 CertificateType::DelegateStake(delegate_stake) => {
325 let stake_cred = RewardAccount::from_bech32(&delegate_stake.stake_key_address)?
326 .to_stake_cred()?;
327 match stake_cred.inner {
328 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
329 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
330 self.required_signatures_vec.push(hash.to_string());
331 }
332 }
333 Ok(Certificate::new(CertificateKind::StakeDelegation {
334 stake_credential: stake_cred,
335 pool_key_hash: delegate_stake.pool_id.clone(),
336 }))?
337 }
338 CertificateType::RegisterPool(register_pool) => {
339 let mut relays: Vec<Relay> = vec![];
340 for relay in ®ister_pool.pool_params.relays {
341 match relay {
342 whisky_common::Relay::SingleHostAddr(single_host_addr) => {
343 relays.push(Relay::new(RelayKind::SingleHostAddr(
344 single_host_addr.port.map(|p| p.into()),
345 single_host_addr.ipv4.clone(),
346 single_host_addr.ipv6.clone(),
347 ))?);
348 }
349 whisky_common::Relay::SingleHostName(single_host_name) => {
350 relays.push(Relay::new(RelayKind::SingleHostName(
351 single_host_name.port.map(|p| p.into()),
352 single_host_name.domain_name.clone(),
353 ))?);
354 }
355 whisky_common::Relay::MultiHostName(multi_host_name) => {
356 relays.push(Relay::new(RelayKind::MultiHostName(
357 multi_host_name.domain_name.clone(),
358 ))?);
359 }
360 }
361 }
362 Ok(Certificate::new(CertificateKind::PoolRegistration {
364 operator: register_pool.pool_params.operator.clone(),
365 vrf_keyhash: register_pool.pool_params.vrf_key_hash.clone(),
366 pledge: register_pool
367 .pool_params
368 .pledge
369 .clone()
370 .parse::<u64>()
371 .map_err(|e| {
372 WError::new(
373 "Certificate - Pool Registration: Invalid pledge amount",
374 &e.to_string(),
375 )
376 })?,
377 cost: register_pool
378 .pool_params
379 .cost
380 .clone()
381 .parse::<u64>()
382 .map_err(|e| {
383 WError::new(
384 "Certificate - Pool Registration: Invalid cost amount",
385 &e.to_string(),
386 )
387 })?,
388 margin: register_pool.pool_params.margin,
389 reward_account: RewardAccount::new(bytes_from_bech32(
390 ®ister_pool.pool_params.reward_address,
391 )?)?,
392 pool_owners: register_pool.pool_params.owners.clone(),
393 relays,
394 pool_metadata: register_pool
395 .pool_params
396 .metadata
397 .clone()
398 .map(|metadata| PoolMetadata::new(metadata.url, metadata.hash).unwrap()),
399 }))?
400 }
401 CertificateType::RetirePool(retire_pool) => {
402 Ok(Certificate::new(CertificateKind::PoolRetirement {
404 pool_key_hash: retire_pool.pool_id.clone(),
405 epoch: retire_pool.epoch.into(),
406 }))?
407 }
408 CertificateType::VoteDelegation(vote_delegation) => {
409 let drep: DRep = match &vote_delegation.drep {
410 whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
411 whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
412 whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
413 };
414 let stake_cred = RewardAccount::from_bech32(&vote_delegation.stake_key_address)?
415 .to_stake_cred()?;
416 match stake_cred.inner {
417 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
418 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
419 self.required_signatures_vec.push(hash.to_string());
420 }
421 }
422 Ok(Certificate::new(CertificateKind::VoteDeleg {
423 stake_credential: stake_cred,
424 drep,
425 }))?
426 }
427 CertificateType::StakeAndVoteDelegation(stake_and_vote_delegation) => {
428 let drep: DRep = match &stake_and_vote_delegation.drep {
429 whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
430 whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
431 whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
432 };
433 let stake_cred =
434 RewardAccount::from_bech32(&stake_and_vote_delegation.stake_key_address)?
435 .to_stake_cred()?;
436 match stake_cred.inner {
437 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
438 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
439 self.required_signatures_vec.push(hash.to_string());
440 }
441 }
442 Ok(Certificate::new(CertificateKind::StakeVoteDeleg {
443 stake_credential: stake_cred,
444 pool_key_hash: stake_and_vote_delegation.pool_key_hash.clone(),
445 drep,
446 }))?
447 }
448 CertificateType::StakeRegistrationAndDelegation(stake_registration_and_delegation) => {
449 let stake_cred = RewardAccount::from_bech32(
450 &stake_registration_and_delegation.stake_key_address,
451 )?
452 .to_stake_cred()?;
453 match stake_cred.inner {
454 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
455 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
456 self.required_signatures_vec.push(hash.to_string());
457 }
458 }
459 Ok(Certificate::new(CertificateKind::StakeRegDeleg {
460 stake_credential: stake_cred,
461 pool_key_hash: stake_registration_and_delegation.pool_key_hash.clone(),
462 amount: stake_registration_and_delegation.coin.into(),
463 }))?
464 }
465 CertificateType::VoteRegistrationAndDelegation(vote_registration_and_delegation) => {
466 let drep: DRep = match &vote_registration_and_delegation.drep {
467 whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
468 whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
469 whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
470 };
471 let stake_cred = RewardAccount::from_bech32(
472 &vote_registration_and_delegation.stake_key_address,
473 )?
474 .to_stake_cred()?;
475 match stake_cred.inner {
476 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
477 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
478 self.required_signatures_vec.push(hash.to_string());
479 }
480 }
481 Ok(Certificate::new(CertificateKind::VoteRegDeleg {
482 stake_credential: stake_cred,
483 drep,
484 amount: vote_registration_and_delegation.coin.into(),
485 }))?
486 }
487 CertificateType::StakeVoteRegistrationAndDelegation(
488 stake_vote_registration_and_delegation,
489 ) => {
490 let drep: DRep = match &stake_vote_registration_and_delegation.drep {
491 whisky_common::DRep::DRepId(drep_id) => DRep::from_bech32(&drep_id)?,
492 whisky_common::DRep::AlwaysAbstain => DRep::new(DRepKind::Abstain)?,
493 whisky_common::DRep::AlwaysNoConfidence => DRep::new(DRepKind::NoConfidence)?,
494 };
495 let stake_cred = RewardAccount::from_bech32(
496 &stake_vote_registration_and_delegation.stake_key_address,
497 )?
498 .to_stake_cred()?;
499 match stake_cred.inner {
500 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
501 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
502 self.required_signatures_vec.push(hash.to_string());
503 }
504 }
505 Ok(Certificate::new(CertificateKind::StakeVoteRegDeleg {
506 stake_credential: stake_cred,
507 pool_key_hash: stake_vote_registration_and_delegation.pool_key_hash.clone(),
508 drep,
509 amount: stake_vote_registration_and_delegation.coin.into(),
510 }))?
511 }
512 CertificateType::CommitteeHotAuth(committee_hot_auth) => {
513 let committee_cold_cred =
514 RewardAccount::from_bech32(&committee_hot_auth.committee_cold_key_address)?
515 .to_stake_cred()?;
516 match committee_cold_cred.inner {
517 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
518 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
519 self.required_signatures_vec.push(hash.to_string());
520 }
521 }
522 Ok(Certificate::new(CertificateKind::AuthCommitteeHot {
523 committee_cold_cred,
524 committee_hot_cred: RewardAccount::from_bech32(
525 &committee_hot_auth.committee_hot_key_address,
526 )?
527 .to_stake_cred()?,
528 }))?
529 }
530 CertificateType::CommitteeColdResign(committee_cold_resign) => {
531 let anchor: Option<Anchor> = match &committee_cold_resign.anchor {
532 Some(anchor_data) => Some(Anchor::new(
533 anchor_data.anchor_url.clone(),
534 anchor_data.anchor_data_hash.clone(),
535 )?),
536 None => None,
537 };
538 let committee_cold_cred =
539 RewardAccount::from_bech32(&committee_cold_resign.committee_cold_key_address)?
540 .to_stake_cred()?;
541 match committee_cold_cred.inner {
542 pallas::ledger::primitives::StakeCredential::ScriptHash(_hash) => {}
543 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
544 self.required_signatures_vec.push(hash.to_string());
545 }
546 }
547 Ok(Certificate::new(CertificateKind::ResignCommitteeCold {
548 committee_cold_cred,
549 anchor,
550 }))?
551 }
552 CertificateType::DRepRegistration(drep_registration) => {
553 let drep: DRep = DRep::from_bech32(&drep_registration.drep_id)?;
554 let drep_cred = match &drep.inner {
555 pallas::ledger::primitives::conway::DRep::Key(hash) => {
556 self.required_signatures_vec.push(hash.to_string());
557 StakeCredential::new(StakeCredentialKind::KeyHash {
558 key_hash_hex: hash.to_string(),
559 })?
560 }
561 pallas::ledger::primitives::conway::DRep::Script(hash) => {
562 StakeCredential::new(StakeCredentialKind::ScriptHash {
563 script_hash_hex: hash.to_string(),
564 })?
565 }
566 _ => {
567 return Err(WError::new(
568 "Certificate - DRep Registration:",
569 "DRep must be either Key or Script type",
570 ));
571 }
572 };
573 Ok(Certificate::new(CertificateKind::RegDRepCert {
574 drep_cred,
575 amount: drep_registration.coin.into(),
576 anchor: drep_registration.anchor.clone().map(|anchor_data| {
577 Anchor::new(anchor_data.anchor_url, anchor_data.anchor_data_hash).unwrap()
578 }),
579 }))?
580 }
581 CertificateType::DRepDeregistration(drep_deregistration) => {
582 let drep: DRep = DRep::from_bech32(&drep_deregistration.drep_id)?;
583 let drep_cred = match &drep.inner {
584 pallas::ledger::primitives::conway::DRep::Key(hash) => {
585 self.required_signatures_vec.push(hash.to_string());
586 StakeCredential::new(StakeCredentialKind::KeyHash {
587 key_hash_hex: hash.to_string(),
588 })?
589 }
590 pallas::ledger::primitives::conway::DRep::Script(hash) => {
591 StakeCredential::new(StakeCredentialKind::ScriptHash {
592 script_hash_hex: hash.to_string(),
593 })?
594 }
595 _ => {
596 return Err(WError::new(
597 "Certificate - DRep Deregistration:",
598 "DRep must be either Key or Script type",
599 ));
600 }
601 };
602 Ok(Certificate::new(CertificateKind::UnRegDRepCert {
603 drep_cred,
604 amount: drep_deregistration.coin.into(),
605 }))?
606 }
607 CertificateType::DRepUpdate(drep_update) => {
608 let drep: DRep = DRep::from_bech32(&drep_update.drep_id)?;
609 let drep_cred = match &drep.inner {
610 pallas::ledger::primitives::conway::DRep::Key(hash) => {
611 self.required_signatures_vec.push(hash.to_string());
612 StakeCredential::new(StakeCredentialKind::KeyHash {
613 key_hash_hex: hash.to_string(),
614 })?
615 }
616 pallas::ledger::primitives::conway::DRep::Script(hash) => {
617 StakeCredential::new(StakeCredentialKind::ScriptHash {
618 script_hash_hex: hash.to_string(),
619 })?
620 }
621 _ => {
622 return Err(WError::new(
623 "Certificate - DRep Update:",
624 "DRep must be either Key or Script type",
625 ));
626 }
627 };
628
629 let anchor: Option<Anchor> = match &drep_update.anchor {
630 Some(anchor_data) => Some(Anchor::new(
631 anchor_data.anchor_url.clone(),
632 anchor_data.anchor_data_hash.clone(),
633 )?),
634 None => None,
635 };
636 Ok(Certificate::new(CertificateKind::UpdateDRepCert {
637 drep_cred,
638 anchor,
639 }))?
640 }
641 }
642 }
643
644 fn process_certificates(
645 &mut self,
646 whisky_certificates: Vec<WhiskyCertificate>,
647 ) -> Result<Option<Vec<Certificate>>, WError> {
648 let mut certificates: Vec<Certificate> = vec![];
649
650 for cert in whisky_certificates.clone() {
651 match cert {
652 BasicCertificate(certificate_type) => {
653 certificates.push(self.process_certificate_type(&certificate_type)?);
654 }
655 ScriptCertificate(script_certificate) => {
656 let cert = self.process_certificate_type(&script_certificate.cert)?;
657 let script_source =
658 script_certificate.script_source.clone().ok_or_else(|| {
659 WError::new(
660 "WhiskyPallas - Processing certificates:",
661 "Script source is missing from script certificate",
662 )
663 })?;
664 self.process_script_source(script_source)?;
665
666 let redeemer = script_certificate.redeemer.clone().ok_or_else(|| {
667 WError::new(
668 "WhiskyPallas - Processing certificates:",
669 "Redeemer is missing from script certificate",
670 )
671 })?;
672 self.certificate_redeemers_vec.push((
673 cert.clone(),
674 Redeemer::new(
675 RedeemerTag::Cert,
676 0,
677 PlutusData::new(redeemer.data)?,
678 ExUnits {
679 mem: redeemer.ex_units.mem,
680 steps: redeemer.ex_units.steps,
681 },
682 )?,
683 ));
684
685 certificates.push(cert);
686 }
687 SimpleScriptCertificate(simple_script_certificate) => {
688 match simple_script_certificate.simple_script_source {
689 Some(simple_script_source) => match simple_script_source {
690 whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
691 provided_simple_script_source,
692 ) => {
693 self.native_scripts_vec.push(NativeScript::new_from_hex(
694 &provided_simple_script_source.script_cbor,
695 )?);
696 }
697 whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
698 inline_simple_script_source,
699 ) => self.ref_inputs_vec.push(TransactionInput::new(
700 &inline_simple_script_source.ref_tx_in.tx_hash,
701 inline_simple_script_source.ref_tx_in.tx_index.into(),
702 )?),
703 },
704 None => {
705 return Err(WError::new(
706 "WhiskyPallas - Processing certificates:",
707 "Simple script source is missing from simple script certificate",
708 ));
709 }
710 };
711 certificates
712 .push(self.process_certificate_type(&simple_script_certificate.cert)?);
713 }
714 }
715 }
716 if certificates.is_empty() {
717 Ok(None)
718 } else {
719 Ok(Some(certificates))
720 }
721 }
722
723 fn process_withdrawals(
724 &mut self,
725 whisky_withdrawals: Vec<WhiskyWithdrawal>,
726 ) -> Result<Option<Vec<(RewardAccount, u64)>>, WError> {
727 let mut withdrawals: Vec<(RewardAccount, u64)> = vec![];
728 for withdrawal in whisky_withdrawals.clone() {
729 match withdrawal {
730 PubKeyWithdrawal(pub_key_withdrawal) => {
731 let reward_account_bytes = bytes_from_bech32(&pub_key_withdrawal.address)?;
732 self.required_signatures_vec
733 .push(reward_account_bytes.clone());
734 let reward_account = RewardAccount::new(reward_account_bytes)?;
735 withdrawals.push((reward_account, pub_key_withdrawal.coin));
736 }
737 PlutusScriptWithdrawal(plutus_script_withdrawal) => {
738 let reward_account_bytes =
739 bytes_from_bech32(&plutus_script_withdrawal.address)?;
740 let reward_account = RewardAccount::new(reward_account_bytes)?;
741 withdrawals.push((reward_account.clone(), plutus_script_withdrawal.coin));
742 let script_source =
743 plutus_script_withdrawal
744 .script_source
745 .clone()
746 .ok_or_else(|| {
747 WError::new(
748 "WhiskyPallas - Processing withdrawals:",
749 "Script source is missing from plutus script withdrawal",
750 )
751 })?;
752 self.process_script_source(script_source)?;
753
754 let redeemer = plutus_script_withdrawal.redeemer.clone().ok_or_else(|| {
755 WError::new(
756 "WhiskyPallas - Processing withdrawals:",
757 "Redeemer is missing from plutus script withdrawal",
758 )
759 })?;
760 self.withdrawal_redeemers_vec.push((
761 reward_account.clone(),
762 Redeemer::new(
763 RedeemerTag::Reward,
764 0,
765 PlutusData::new(redeemer.data)?,
766 ExUnits {
767 mem: redeemer.ex_units.mem,
768 steps: redeemer.ex_units.steps,
769 },
770 )?,
771 ));
772 }
773 SimpleScriptWithdrawal(simple_script_withdrawal) => {
774 let reward_account_bytes =
775 bytes_from_bech32(&simple_script_withdrawal.address)?;
776 let reward_account = RewardAccount::new(reward_account_bytes)?;
777 withdrawals.push((reward_account.clone(), simple_script_withdrawal.coin));
778 match &simple_script_withdrawal.script_source {
779 Some(simple_script_source) => match simple_script_source {
780 whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
781 provided_simple_script_source,
782 ) => {
783 self.native_scripts_vec.push(NativeScript::new_from_hex(
784 &provided_simple_script_source.script_cbor,
785 )?);
786 }
787 whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
788 inline_simple_script_source,
789 ) => self.ref_inputs_vec.push(TransactionInput::new(
790 &inline_simple_script_source.ref_tx_in.tx_hash,
791 inline_simple_script_source.ref_tx_in.tx_index.into(),
792 )?),
793 },
794 None => {
795 return Err(WError::new(
796 "WhiskyPallas - Processing withdrawals:",
797 "Simple script source is missing from simple script withdrawal",
798 ));
799 }
800 };
801 }
802 }
803 }
804 Ok(if withdrawals.is_empty() {
805 None
806 } else {
807 Some(withdrawals)
808 })
809 }
810
811 fn process_mints(
812 &mut self,
813 whisky_mints: Vec<MintItem>,
814 ) -> Result<Option<MultiassetNonZeroInt>, WError> {
815 let mut mints: Vec<(String, Vec<(String, i64)>)> = vec![];
816 for mint in whisky_mints.clone() {
817 match mint {
818 whisky_common::MintItem::ScriptMint(script_mint) => {
819 let mint_param = script_mint.mint;
820 let existing_policy =
821 mints.iter_mut().find(|mint| mint.0 == mint_param.policy_id);
822 if existing_policy.is_some() {
823 let policy_mint = existing_policy.unwrap();
824 policy_mint.1.push((
825 mint_param.asset_name,
826 mint_param.amount.try_into().map_err(|_| {
827 WError::new(
828 "WhiskyPallas - Processing mints:",
829 "Invalid mint amount",
830 )
831 })?,
832 ));
833 } else {
834 mints.push((
835 mint_param.policy_id.clone(),
836 vec![(
837 mint_param.asset_name,
838 mint_param.amount.try_into().map_err(|_| {
839 WError::new(
840 "WhiskyPallas - Processing mints:",
841 "Invalid mint amount",
842 )
843 })?,
844 )],
845 ));
846 }
847
848 let script_source = script_mint.script_source.clone().ok_or_else(|| {
849 WError::new(
850 "WhiskyPallas - Processing mints:",
851 "Script source is missing from script mint",
852 )
853 })?;
854 self.process_script_source(script_source)?;
855
856 let redeemer = script_mint.redeemer.clone().ok_or_else(|| {
857 WError::new(
858 "WhiskyPallas - Processing mints:",
859 "Redeemer is missing from script mint",
860 )
861 })?;
862 self.mint_redeemers_vec.push((
863 mint_param.policy_id.clone(),
864 Redeemer::new(
865 RedeemerTag::Mint,
866 0,
867 PlutusData::new(redeemer.data)?,
868 ExUnits {
869 mem: redeemer.ex_units.mem,
870 steps: redeemer.ex_units.steps,
871 },
872 )?,
873 ));
874 }
875 whisky_common::MintItem::SimpleScriptMint(simple_script_mint) => {
876 let mint_param = simple_script_mint.mint;
877 let existing_policy =
878 mints.iter_mut().find(|mint| mint.0 == mint_param.policy_id);
879 if existing_policy.is_some() {
880 let policy_mint = existing_policy.unwrap();
881 policy_mint.1.push((
882 mint_param.asset_name,
883 mint_param.amount.try_into().map_err(|_| {
884 WError::new(
885 "WhiskyPallas - Processing mints:",
886 "Invalid mint amount",
887 )
888 })?,
889 ));
890 } else {
891 mints.push((
892 mint_param.policy_id.clone(),
893 vec![(
894 mint_param.asset_name,
895 mint_param.amount.try_into().map_err(|_| {
896 WError::new(
897 "WhiskyPallas - Processing mints:",
898 "Invalid mint amount",
899 )
900 })?,
901 )],
902 ));
903 }
904
905 match &simple_script_mint.script_source {
906 Some(simple_script_source) => match simple_script_source {
907 whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
908 provided_simple_script_source,
909 ) => {
910 self.native_scripts_vec.push(NativeScript::new_from_hex(
911 &provided_simple_script_source.script_cbor,
912 )?);
913 }
914 whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
915 inline_simple_script_source,
916 ) => self.ref_inputs_vec.push(TransactionInput::new(
917 &inline_simple_script_source.ref_tx_in.tx_hash,
918 inline_simple_script_source.ref_tx_in.tx_index.into(),
919 )?),
920 },
921 None => {
922 return Err(WError::new(
923 "WhiskyPallas - Processing mints:",
924 "Simple script source is missing from simple script mint",
925 ));
926 }
927 };
928 }
929 }
930 }
931 Ok(if mints.is_empty() {
932 None
933 } else {
934 Some(MultiassetNonZeroInt::new(mints)?)
935 })
936 }
937
938 fn process_collaterals(
939 &mut self,
940 whisky_collaterals: Vec<PubKeyTxIn>,
941 ) -> Result<Option<Vec<TransactionInput>>, WError> {
942 let mut collaterals: Vec<TransactionInput> = vec![];
943 for collateral in whisky_collaterals.clone() {
944 self.required_signatures_vec
945 .push(bytes_from_bech32(&collateral.tx_in.address.unwrap())?);
946 let transaction_input =
947 TransactionInput::new(&collateral.tx_in.tx_hash, collateral.tx_in.tx_index.into())?;
948 collaterals.push(transaction_input.clone());
949 self.collaterals_map.insert(
950 transaction_input.clone(),
951 convert_value(&collateral.tx_in.amount.unwrap())?,
952 );
953 }
954 Ok(if collaterals.is_empty() {
955 None
956 } else {
957 Some(collaterals)
958 })
959 }
960
961 fn process_required_signers(
962 &mut self,
963 whisky_required_signers: Vec<String>,
964 ) -> Result<Option<RequiredSigners>, WError> {
965 let mut required_signers: Vec<String> = vec![];
966 for signer in whisky_required_signers.clone() {
967 self.required_signatures_vec.push(signer.clone());
968 required_signers.push(signer);
969 }
970 Ok(if required_signers.is_empty() {
971 None
972 } else {
973 Some(RequiredSigners::new(required_signers)?)
974 })
975 }
976
977 fn process_total_collateral(
978 &mut self,
979 whisky_total_collateral: Option<String>,
980 ) -> Result<Option<u64>, WError> {
981 if let Some(total_collateral) = whisky_total_collateral.clone() {
982 Ok(Some(total_collateral.parse::<u64>().map_err(|e| {
983 WError::new(
984 "WhiskyPallas - Processing total collateral:",
985 &format!("Failed to parse total collateral: {}", e.to_string()),
986 )
987 })?))
988 } else {
989 Ok(None)
990 }
991 }
992
993 fn process_script_source(&mut self, script_source: ScriptSource) -> Result<(), WError> {
994 match script_source {
995 ProvidedScriptSource(provided_script_source) => {
996 match provided_script_source.language_version {
997 LanguageVersion::V1 => {
998 self.plutus_v1_scripts_vec
999 .push(PlutusScript::<1>::new(provided_script_source.script_cbor)?);
1000 self.plutus_v1_used = true;
1001 }
1002 LanguageVersion::V2 => {
1003 self.plutus_v2_scripts_vec
1004 .push(PlutusScript::<2>::new(provided_script_source.script_cbor)?);
1005 self.plutus_v2_used = true;
1006 }
1007 LanguageVersion::V3 => {
1008 self.plutus_v3_scripts_vec
1009 .push(PlutusScript::<3>::new(provided_script_source.script_cbor)?);
1010 self.plutus_v3_used = true;
1011 }
1012 }
1013 }
1014 InlineScriptSource(inline_script_source) => {
1015 self.ref_inputs_vec.push(TransactionInput::new(
1016 &inline_script_source.ref_tx_in.tx_hash,
1017 inline_script_source.ref_tx_in.tx_index.into(),
1018 )?);
1019 self.script_source_ref_inputs
1020 .push(inline_script_source.ref_tx_in.clone());
1021 match inline_script_source.language_version {
1022 LanguageVersion::V1 => {
1023 self.plutus_v1_used = true;
1024 }
1025 LanguageVersion::V2 => {
1026 self.plutus_v2_used = true;
1027 }
1028 LanguageVersion::V3 => {
1029 self.plutus_v3_used = true;
1030 }
1031 }
1032 }
1033 };
1034 Ok(())
1035 }
1036
1037 fn process_vote_type(
1038 &mut self,
1039 vote_type: &whisky_common::VoteType,
1040 ) -> Result<(Voter, Vec<(GovActionId, VotingProdecedure)>), WError> {
1041 let voter = match &vote_type.voter {
1042 whisky_common::Voter::ConstitutionalCommitteeHotCred(credential) => {
1043 match credential {
1044 whisky_common::Credential::KeyHash(key_hash_hex) => {
1045 self.required_signatures_vec.push(key_hash_hex.clone());
1046 Voter::new(VoterKind::ConstitutionalCommitteKey {
1047 key_hash: key_hash_hex.clone(),
1048 })
1049 }
1050 whisky_common::Credential::ScriptHash(script_hash_hex) => {
1051 Voter::new(VoterKind::ConstitutionalCommitteScript {
1052 script_hash: script_hash_hex.clone(),
1053 })
1054 }
1055 }
1056 }?,
1057 whisky_common::Voter::DRepId(drep_id) => {
1058 let drep = DRep::from_bech32(&drep_id)?;
1059 match drep.inner {
1060 pallas::ledger::primitives::conway::DRep::Key(hash) => {
1061 self.required_signatures_vec.push(hash.to_string());
1062 Voter::new(VoterKind::DrepKey {
1063 key_hash: hash.to_string(),
1064 })
1065 }
1066 pallas::ledger::primitives::conway::DRep::Script(hash) => {
1067 Voter::new(VoterKind::DrepScript {
1068 script_hash: hash.to_string(),
1069 })
1070 }
1071 _ => {
1072 return Err(WError::new(
1073 "Voting Procedure - Voter:",
1074 "DRep must be either Key or Script type",
1075 ));
1076 }
1077 }
1078 }?,
1079 whisky_common::Voter::StakingPoolKeyHash(reward_account) => {
1080 let stake_cred = RewardAccount::from_bech32(&reward_account)?.to_stake_cred()?;
1081 match stake_cred.inner {
1082 pallas::ledger::primitives::StakeCredential::ScriptHash(hash) => {
1083 Voter::new(VoterKind::StakePoolKey {
1084 pool_key_hash: hash.to_string(),
1085 })
1086 }
1087 pallas::ledger::primitives::StakeCredential::AddrKeyhash(hash) => {
1088 self.required_signatures_vec.push(hash.to_string());
1089 Voter::new(VoterKind::StakePoolKey {
1090 pool_key_hash: hash.to_string(),
1091 })
1092 }
1093 }
1094 }?,
1095 };
1096
1097 let gov_action_id = GovActionId::new(
1098 &vote_type.gov_action_id.tx_hash,
1099 vote_type.gov_action_id.tx_index.into(),
1100 )?;
1101
1102 let voting_procedure = VotingProdecedure::new(
1103 match &vote_type.voting_procedure.vote_kind {
1104 whisky_common::VoteKind::No => Vote::new(VoteKind::No)?,
1105 whisky_common::VoteKind::Yes => Vote::new(VoteKind::Yes)?,
1106 whisky_common::VoteKind::Abstain => Vote::new(VoteKind::Abstain)?,
1107 },
1108 match &vote_type.voting_procedure.anchor {
1109 Some(anchor_data) => Some(Anchor::new(
1110 anchor_data.anchor_url.clone(),
1111 anchor_data.anchor_data_hash.clone(),
1112 )?),
1113 None => None,
1114 },
1115 );
1116 Ok((voter, vec![(gov_action_id, voting_procedure)]))
1117 }
1118
1119 fn process_voting_procedures(
1120 &mut self,
1121 whisky_votes: Vec<WhiskyVote>,
1122 ) -> Result<Option<Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)>>, WError> {
1123 let mut voting_procedures: Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)> = vec![];
1124
1125 for vote in whisky_votes.clone() {
1126 match vote {
1127 whisky_common::Vote::BasicVote(vote_type) => {
1128 let (voter, procedures) = self.process_vote_type(&vote_type)?;
1129 if let Some(existing_voter) =
1131 voting_procedures.iter_mut().find(|(v, _)| *v == voter)
1132 {
1133 existing_voter.1.extend(procedures);
1134 } else {
1135 voting_procedures.push((voter, procedures));
1136 }
1137 }
1138 whisky_common::Vote::ScriptVote(script_vote) => {
1139 let (voter, procedures) = self.process_vote_type(&script_vote.vote)?;
1140 if let Some(existing_voter) =
1142 voting_procedures.iter_mut().find(|(v, _)| *v == voter)
1143 {
1144 existing_voter.1.extend(procedures);
1145 } else {
1146 voting_procedures.push((voter.clone(), procedures));
1147 }
1148
1149 let script_source = script_vote.script_source.clone().ok_or_else(|| {
1150 WError::new(
1151 "WhiskyPallas - Processing voting procedures:",
1152 "Script source is missing from script vote",
1153 )
1154 })?;
1155 self.process_script_source(script_source)?;
1156
1157 let redeemer = script_vote.redeemer.clone().ok_or_else(|| {
1158 WError::new(
1159 "WhiskyPallas - Processing voting procedures:",
1160 "Redeemer is missing from script vote",
1161 )
1162 })?;
1163 self.vote_redeemers_vec.push((
1164 voter.clone(),
1165 Redeemer::new(
1166 RedeemerTag::Vote,
1167 0,
1168 PlutusData::new(redeemer.data)?,
1169 ExUnits {
1170 mem: redeemer.ex_units.mem,
1171 steps: redeemer.ex_units.steps,
1172 },
1173 )?,
1174 ));
1175 }
1176 whisky_common::Vote::SimpleScriptVote(simple_script_vote) => {
1177 let (voter, procedures) = self.process_vote_type(&simple_script_vote.vote)?;
1178 if let Some(existing_voter) =
1180 voting_procedures.iter_mut().find(|(v, _)| *v == voter)
1181 {
1182 existing_voter.1.extend(procedures);
1183 } else {
1184 voting_procedures.push((voter, procedures));
1185 }
1186
1187 match &simple_script_vote.simple_script_source {
1188 Some(simple_script_source) => match simple_script_source {
1189 whisky_common::SimpleScriptSource::ProvidedSimpleScriptSource(
1190 provided_simple_script_source,
1191 ) => {
1192 self.native_scripts_vec.push(NativeScript::new_from_hex(
1193 &provided_simple_script_source.script_cbor,
1194 )?);
1195 }
1196 whisky_common::SimpleScriptSource::InlineSimpleScriptSource(
1197 inline_simple_script_source,
1198 ) => self.ref_inputs_vec.push(TransactionInput::new(
1199 &inline_simple_script_source.ref_tx_in.tx_hash,
1200 inline_simple_script_source.ref_tx_in.tx_index.into(),
1201 )?),
1202 },
1203 None => {
1204 return Err(WError::new(
1205 "WhiskyPallas - Processing voting procedures:",
1206 "Simple script source is missing from simple script vote",
1207 ));
1208 }
1209 };
1210 }
1211 }
1212 }
1213
1214 if voting_procedures.is_empty() {
1215 Ok(None)
1216 } else {
1217 Ok(Some(voting_procedures))
1218 }
1219 }
1220
1221 fn process_reference_inputs(
1222 &mut self,
1223 whisky_ref_inputs: Vec<RefTxIn>,
1224 whisky_inputs: Vec<TxIn>,
1225 ) -> Result<Option<Vec<TransactionInput>>, WError> {
1226 for ref_input in whisky_ref_inputs.clone() {
1227 self.ref_inputs_vec.push(TransactionInput::new(
1228 &ref_input.tx_hash,
1229 ref_input.tx_index.into(),
1230 )?);
1231 self.script_source_ref_inputs.push(ref_input.clone());
1232 }
1233 let final_ref_inputs: Vec<TransactionInput> = self
1234 .ref_inputs_vec
1235 .clone()
1236 .iter()
1237 .filter(|ref_input| {
1238 whisky_inputs
1240 .iter()
1241 .find(|input| {
1242 input.to_utxo().input.tx_hash == ref_input.inner.transaction_id.to_string()
1243 && input.to_utxo().input.output_index == ref_input.inner.index as u32
1244 })
1245 .is_none()
1246 })
1247 .cloned()
1248 .collect();
1249 for pallas_ref_input in final_ref_inputs.iter() {
1250 let Some(script_source_tx_in) =
1251 self.script_source_ref_inputs
1252 .iter()
1253 .find(|script_source_tx_in| {
1254 script_source_tx_in.tx_hash
1255 == pallas_ref_input.inner.transaction_id.to_string()
1256 && script_source_tx_in.tx_index == pallas_ref_input.inner.index as u32
1257 })
1258 else {
1259 continue;
1260 };
1261 let Some(script_size) = script_source_tx_in.script_size else {
1262 continue;
1263 };
1264 self.total_script_size += script_size;
1265 }
1266 Ok(Some(final_ref_inputs))
1267 }
1268
1269 fn process_datum_source(&mut self, datum_source: DatumSource) -> Result<(), WError> {
1270 match datum_source {
1271 ProvidedDatumSource(provided_datum_source) => {
1272 self.plutus_data_vec
1273 .push(PlutusData::new(provided_datum_source.data)?);
1274 }
1275 InlineDatumSource(inline_datum_source) => {
1276 self.ref_inputs_vec.push(TransactionInput::new(
1277 &inline_datum_source.tx_hash,
1278 inline_datum_source.tx_index.into(),
1279 )?)
1280 }
1281 };
1282 Ok(())
1283 }
1284
1285 fn process_witness_set(
1286 &'_ mut self,
1287 tx_inputs: Vec<TransactionInput>,
1288 certificates: Option<Vec<Certificate>>,
1289 withdrawals: Option<Vec<(RewardAccount, u64)>>,
1290 mints: Option<MultiassetNonZeroInt>,
1291 votes: Option<Vec<(Voter, Vec<(GovActionId, VotingProdecedure)>)>>,
1292 ) -> Result<WitnessSet<'_>, WError> {
1293 let native_scripts = if self.native_scripts_vec.is_empty() {
1294 None
1295 } else {
1296 Some(self.native_scripts_vec.clone())
1297 };
1298 let plutus_v1_scripts = if self.plutus_v1_scripts_vec.is_empty() {
1299 None
1300 } else {
1301 Some(self.plutus_v1_scripts_vec.clone())
1302 };
1303 let plutus_v2_scripts = if self.plutus_v2_scripts_vec.is_empty() {
1304 None
1305 } else {
1306 Some(self.plutus_v2_scripts_vec.clone())
1307 };
1308 let plutus_v3_scripts = if self.plutus_v3_scripts_vec.is_empty() {
1309 None
1310 } else {
1311 Some(self.plutus_v3_scripts_vec.clone())
1312 };
1313 let plutus_data = if self.plutus_data_vec.is_empty() {
1314 None
1315 } else {
1316 Some(self.plutus_data_vec.clone())
1317 };
1318 let mut redeemers: Vec<PallasRedeemer> = vec![];
1319 for (input, redeemer) in self.input_redeemers_vec.clone() {
1321 let index = tx_inputs.iter().position(|tx_input| {
1323 tx_input.inner.transaction_id.to_string() == input.inner.transaction_id.to_string()
1324 && tx_input.inner.index == input.inner.index
1325 });
1326 if let Some(idx) = index {
1327 redeemers.push(PallasRedeemer {
1328 tag: PallasRedeemerTag::Spend,
1329 index: idx as u32,
1330 data: redeemer.inner.data.clone(),
1331 ex_units: redeemer.inner.ex_units.clone(),
1332 })
1333 } else {
1334 return Err(WError::new(
1335 "WhiskyPallas - Processing witness set:",
1336 "Input for redeemer not found in transaction inputs",
1337 ));
1338 }
1339 }
1340 let certificates = certificates.unwrap_or_default();
1342 for (cert, redeemer) in self.certificate_redeemers_vec.clone() {
1343 let index = certificates.iter().position(|c| c == &cert);
1345 if let Some(idx) = index {
1346 redeemers.push(PallasRedeemer {
1347 tag: PallasRedeemerTag::Cert,
1348 index: idx as u32,
1349 data: redeemer.inner.data.clone(),
1350 ex_units: redeemer.inner.ex_units.clone(),
1351 })
1352 } else {
1353 return Err(WError::new(
1354 "WhiskyPallas - Processing witness set:",
1355 "Certificate for redeemer not found in transaction certificates",
1356 ));
1357 }
1358 }
1359 let withdrawals = withdrawals.unwrap_or_default();
1360 for (reward_account, redeemer) in self.withdrawal_redeemers_vec.clone() {
1362 let index = withdrawals.iter().position(|w| w.0 == reward_account);
1364 if let Some(idx) = index {
1365 redeemers.push(PallasRedeemer {
1366 tag: PallasRedeemerTag::Reward,
1367 index: idx as u32,
1368 data: redeemer.inner.data.clone(),
1369 ex_units: redeemer.inner.ex_units.clone(),
1370 })
1371 } else {
1372 return Err(WError::new(
1373 "WhiskyPallas - Processing witness set:",
1374 "Withdrawal for redeemer not found in transaction withdrawals",
1375 ));
1376 }
1377 }
1378 let mints = mints.unwrap_or(MultiassetNonZeroInt::new(vec![])?);
1379 for (policy_id, redeemer) in self.mint_redeemers_vec.clone() {
1381 let index = mints
1383 .inner
1384 .keys()
1385 .position(|pid| pid.to_string() == policy_id);
1386
1387 if let Some(idx) = index {
1388 redeemers.push(PallasRedeemer {
1389 tag: PallasRedeemerTag::Mint,
1390 index: idx as u32,
1391 data: redeemer.inner.data.clone(),
1392 ex_units: redeemer.inner.ex_units.clone(),
1393 })
1394 } else {
1395 return Err(WError::new(
1396 "WhiskyPallas - Processing witness set:",
1397 "Mint for redeemer not found in transaction mints",
1398 ));
1399 }
1400 }
1401
1402 let votes = votes.unwrap_or_default();
1404 for (voter, redeemer) in self.vote_redeemers_vec.clone() {
1405 let index = votes.iter().position(|(v, _)| *v == voter);
1407 if let Some(idx) = index {
1408 redeemers.push(PallasRedeemer {
1409 tag: PallasRedeemerTag::Vote,
1410 index: idx as u32,
1411 data: redeemer.inner.data.clone(),
1412 ex_units: redeemer.inner.ex_units.clone(),
1413 })
1414 } else {
1415 return Err(WError::new(
1416 "WhiskyPallas - Processing witness set:",
1417 "Vote for redeemer not found in transaction votes",
1418 ));
1419 }
1420 }
1421
1422 WitnessSet::new(
1423 None,
1424 native_scripts,
1425 None,
1426 plutus_v1_scripts,
1427 plutus_data,
1428 if redeemers.is_empty() {
1429 None
1430 } else {
1431 Some(
1432 redeemers
1433 .iter()
1434 .map(|redeemer| Redeemer {
1435 inner: redeemer.clone(),
1436 })
1437 .collect(),
1438 )
1439 },
1440 plutus_v2_scripts,
1441 plutus_v3_scripts,
1442 )
1443 }
1444
1445 pub fn build_tx(
1446 &mut self,
1447 tx_builder_body: TxBuilderBody,
1448 balanced: bool,
1449 ) -> Result<String, WError> {
1450 let inputs = self.process_inputs(tx_builder_body.inputs.clone())?;
1451 let mut outputs = self.process_outputs(tx_builder_body.outputs)?;
1452 let ttl = tx_builder_body.validity_range.invalid_hereafter;
1453 let certificates = self.process_certificates(tx_builder_body.certificates)?;
1454 let withdrawals = self.process_withdrawals(tx_builder_body.withdrawals)?;
1455 let validity_interval_start = tx_builder_body.validity_range.invalid_before;
1456 let mints = self.process_mints(tx_builder_body.mints)?;
1457 let collaterals = self.process_collaterals(tx_builder_body.collaterals)?;
1458 let required_signers =
1459 self.process_required_signers(tx_builder_body.required_signatures)?;
1460 let network = tx_builder_body
1461 .network
1462 .clone()
1463 .unwrap_or(whisky_common::Network::Mainnet);
1464 let network_id = match tx_builder_body.network.clone() {
1465 Some(network) => match network {
1466 whisky_common::Network::Mainnet => Some(NetworkId::new(NetworkIdKind::Mainnet)),
1467 _ => Some(NetworkId::new(NetworkIdKind::Testnet)),
1468 },
1469 None => None,
1470 };
1471 let total_collateral = self.process_total_collateral(tx_builder_body.total_collateral)?;
1472 let reference_inputs = self
1473 .process_reference_inputs(tx_builder_body.reference_inputs, tx_builder_body.inputs)?;
1474 let voting_procedures = self.process_voting_procedures(tx_builder_body.votes)?;
1475 let cost_models = get_cost_models_from_network(&network);
1476 let plutus_version: Option<u8> = if self.plutus_v3_used {
1477 Some(2)
1478 } else if self.plutus_v2_used {
1479 Some(1)
1480 } else if self.plutus_v1_used {
1481 Some(0)
1482 } else {
1483 None
1484 };
1485 let total_script_size = self.total_script_size;
1486 let protocol_params = self.protocol_params.clone();
1487 let inputs_map = self.inputs_map.clone();
1488 let required_signatures_vec = self.required_signatures_vec.clone();
1489 let witness_set = self.process_witness_set(
1490 inputs.clone(),
1491 certificates.clone(),
1492 withdrawals.clone(),
1493 mints.clone(),
1494 voting_procedures.clone(),
1495 )?;
1496 let script_data_hash = match plutus_version {
1497 Some(version) => {
1498 let cost_model = match version {
1499 0 => cost_models.get(0),
1500 1 => cost_models.get(1),
1501 2 => cost_models.get(2),
1502 _ => None,
1503 };
1504 let language_view = cost_model.map(|cm| LanguageView(version, cm.clone()));
1505 Some(
1506 ScriptData::build_for(&witness_set.inner, &language_view)
1507 .unwrap()
1508 .hash()
1509 .to_string(),
1510 )
1511 }
1512 None => None,
1513 };
1514 let fee = match tx_builder_body.fee {
1515 Some(fee) => fee.parse::<u64>().map_err(|e| {
1516 WError::new(
1517 "WhiskyPallas - Building transaction:",
1518 &format!("Failed to parse fee: {}", e.to_string()),
1519 )
1520 })?,
1521 None => {
1522 let mock_tx_body = TransactionBody::new(
1523 inputs.clone(),
1524 outputs.clone(),
1525 18446744073709551615, ttl,
1527 certificates.clone(),
1528 withdrawals.clone(),
1529 None,
1530 validity_interval_start,
1531 mints.clone(),
1532 script_data_hash.clone(),
1533 collaterals.clone(),
1534 required_signers.clone(),
1535 network_id.clone(),
1536 None,
1537 total_collateral,
1538 reference_inputs.clone(),
1539 voting_procedures.clone(),
1540 None, None, None, )?;
1544 let mock_witness_set = PallasWitnessSet {
1545 vkeywitness: required_signatures_to_mock_witnesses(
1546 required_signatures_vec.clone(),
1547 ),
1548 native_script: witness_set.inner.native_script.clone(),
1549 bootstrap_witness: witness_set.inner.bootstrap_witness.clone(),
1550 plutus_v1_script: witness_set.inner.plutus_v1_script.clone(),
1551 plutus_data: witness_set.inner.plutus_data.clone(),
1552 redeemer: witness_set.inner.redeemer.clone(),
1553 plutus_v2_script: witness_set.inner.plutus_v2_script.clone(),
1554 plutus_v3_script: witness_set.inner.plutus_v3_script.clone(),
1555 };
1556 let mock_tx = Transaction::new(
1557 mock_tx_body,
1558 WitnessSet {
1559 inner: mock_witness_set,
1560 },
1561 true,
1562 None,
1563 )?;
1564 calculate_fee(mock_tx, total_script_size, protocol_params.clone())?
1565 }
1566 };
1567
1568 if balanced {
1569 let mut change_value: Value = Value::new(0, None);
1570 for (_, value) in inputs_map {
1571 change_value = change_value.add(&value)?;
1572 }
1573 for (_, amount) in withdrawals.clone().unwrap_or_default() {
1574 change_value = change_value.add(&Value::new(amount, None))?;
1575 }
1576 let mut positive_mint_value = Value::new(0, None);
1577 let mut negative_mint_value = Value::new(0, None);
1578 if let Some(ma_non_zero_int) = mints.clone() {
1579 for (policy_id, assets) in ma_non_zero_int.inner {
1580 for (asset_name, amount) in assets {
1581 if i64::from(amount) > 0 {
1582 positive_mint_value = positive_mint_value.add(&Value::new(
1583 0,
1584 Some(MultiassetPositiveCoin::new(vec![(
1585 policy_id.to_string(),
1586 vec![(
1587 asset_name.to_string(),
1588 u64::try_from(i64::from(amount)).map_err(|_| {
1589 WError::new(
1590 "WhiskyPallas - Building transaction:",
1591 "Invalid mint amount while balancing change output",
1592 )
1593 })?,
1594 )],
1595 )])?),
1596 ))?;
1597 } else {
1598 negative_mint_value = negative_mint_value.add(&Value::new(
1599 0,
1600 Some(MultiassetPositiveCoin::new(vec![(
1601 policy_id.to_string(),
1602 vec![(
1603 asset_name.to_string(),
1604 u64::try_from(-i64::from(amount)).map_err(|_| {
1605 WError::new(
1606 "WhiskyPallas - Building transaction:",
1607 "Invalid mint amount while balancing change output",
1608 )
1609 })?,
1610 )],
1611 )])?),
1612 ))?;
1613 }
1614 }
1615 }
1616 }
1617 change_value = change_value.add(&positive_mint_value)?;
1618 change_value = change_value.sub(&negative_mint_value).map_err(|_| {
1619 WError::new(
1620 "WhiskyPallas - Balancing transaction",
1621 "Burn values not covered by inputs",
1622 )
1623 })?;
1624 for output in outputs.iter() {
1625 let output_value = match &output.inner {
1626 pallas::ledger::primitives::babbage::GenTransactionOutput::PostAlonzo(
1627 tx_out,
1628 ) => tx_out.value.clone(),
1629 pallas::ledger::primitives::babbage::GenTransactionOutput::Legacy(tx_out) => {
1630 let legacy_value = &tx_out.amount;
1631 let coerced_value: PallasValue = match legacy_value {
1632 pallas::ledger::primitives::alonzo::Value::Coin(coin) => {
1633 PallasValue::Coin(coin.clone())
1634 }
1635 pallas::ledger::primitives::alonzo::Value::Multiasset(
1636 coin,
1637 asset_map,
1638 ) => {
1639 let converted_asset_map = asset_map
1640 .into_iter()
1641 .map(|(policy_id, assets)| {
1642 let converted_assets = assets
1643 .into_iter()
1644 .map(|(asset_name, amount)| {
1645 (
1646 asset_name.clone(),
1647 PositiveCoin::try_from(amount.clone()).unwrap(),
1648 )
1649 })
1650 .collect();
1651 (policy_id.clone(), converted_assets)
1652 })
1653 .collect();
1654 PallasValue::Multiasset(coin.clone(), converted_asset_map)
1655 }
1656 };
1657 coerced_value
1658 }
1659 };
1660 change_value = change_value
1661 .sub(&Value {
1662 inner: output_value,
1663 })
1664 .map_err(|e| {
1665 WError::new(
1666 "WhiskyPallas - Building transaction:",
1667 &format!(
1668 "Error while balancing change output, inputs less than outputs: {}",
1669 e.to_string()
1670 ),
1671 )
1672 })?;
1673 }
1674 if let Some(certs) = certificates.clone() {
1675 for cert in certs {
1676 match cert.inner {
1677 pallas::ledger::primitives::conway::Certificate::StakeRegistration(_) => {
1678 change_value.sub(&Value::new(protocol_params.key_deposit, None))?;
1679 }
1680 pallas::ledger::primitives::conway::Certificate::StakeDeregistration(_) => {
1681 change_value =
1682 change_value.add(&Value::new(protocol_params.key_deposit, None))?;
1683 }
1684 pallas::ledger::primitives::conway::Certificate::StakeDelegation(_, _) => {}
1685 pallas::ledger::primitives::conway::Certificate::PoolRegistration {
1686 operator: _,
1687 vrf_keyhash: _,
1688 pledge: _,
1689 cost: _,
1690 margin: _,
1691 reward_account: _,
1692 pool_owners: _,
1693 relays: _,
1694 pool_metadata: _,
1695 } => {
1696 change_value = change_value
1697 .sub(&Value::new(protocol_params.pool_deposit, None))?;
1698 }
1699 pallas::ledger::primitives::conway::Certificate::PoolRetirement(_, _) => {
1700 change_value = change_value
1701 .add(&Value::new(protocol_params.pool_deposit, None))?;
1702 }
1703 pallas::ledger::primitives::conway::Certificate::Reg(_, coin) => {
1704 change_value = change_value.sub(&Value::new(coin, None))?;
1705 }
1706 pallas::ledger::primitives::conway::Certificate::UnReg(_, coin) => {
1707 change_value = change_value.add(&Value::new(coin, None))?;
1708 }
1709 pallas::ledger::primitives::conway::Certificate::VoteDeleg(_, _) => {}
1710 pallas::ledger::primitives::conway::Certificate::StakeVoteDeleg(
1711 _,
1712 _,
1713 _,
1714 ) => {}
1715 pallas::ledger::primitives::conway::Certificate::StakeRegDeleg(
1716 _,
1717 _,
1718 coin,
1719 ) => {
1720 change_value = change_value.sub(&Value::new(coin, None))?;
1721 }
1722 pallas::ledger::primitives::conway::Certificate::VoteRegDeleg(
1723 _,
1724 _,
1725 coin,
1726 ) => {
1727 change_value = change_value.sub(&Value::new(coin, None))?;
1728 }
1729 pallas::ledger::primitives::conway::Certificate::StakeVoteRegDeleg(
1730 _,
1731 _,
1732 _,
1733 coin,
1734 ) => {
1735 change_value = change_value.sub(&Value::new(coin, None))?;
1736 }
1737 pallas::ledger::primitives::conway::Certificate::AuthCommitteeHot(_, _) => {
1738 }
1739 pallas::ledger::primitives::conway::Certificate::ResignCommitteeCold(
1740 _,
1741 _,
1742 ) => {}
1743 pallas::ledger::primitives::conway::Certificate::RegDRepCert(
1744 _,
1745 coin,
1746 _,
1747 ) => {
1748 change_value = change_value.sub(&Value::new(coin, None))?;
1749 }
1750 pallas::ledger::primitives::conway::Certificate::UnRegDRepCert(_, coin) => {
1751 change_value = change_value.add(&Value::new(coin, None))?;
1752 }
1753 pallas::ledger::primitives::conway::Certificate::UpdateDRepCert(_, _) => {}
1754 }
1755 }
1756 }
1757 change_value = change_value.sub(&Value::new(fee, None)).map_err(|e| {
1758 WError::new(
1759 "WhiskyPallas - Building transaction:",
1760 &format!(
1761 "Error while balancing change output, inputs less than outputs + fee: {}",
1762 e.to_string()
1763 ),
1764 )
1765 })?;
1766 outputs.push(TransactionOutput::new(
1767 &bytes_from_bech32(&tx_builder_body.change_address)?,
1768 change_value,
1769 None,
1770 None,
1771 )?)
1772 }
1773
1774 let tx_body = TransactionBody::new(
1775 inputs,
1776 outputs,
1777 fee,
1778 ttl,
1779 certificates,
1780 withdrawals,
1781 None,
1782 validity_interval_start,
1783 mints,
1784 script_data_hash,
1785 collaterals,
1786 required_signers,
1787 network_id,
1788 None,
1789 total_collateral,
1790 reference_inputs,
1791 voting_procedures,
1792 None, None, None, )?;
1796 let transaction_bytes = Transaction::new(tx_body, witness_set, true, None)?
1797 .inner
1798 .encode_fragment()
1799 .map_err(|e| {
1800 WError::new(
1801 "WhiskyPallas - Building transaction:",
1802 &format!("Encoding failed at Transaction: {}", e.to_string()),
1803 )
1804 })?;
1805 Ok(hex::encode(transaction_bytes))
1806 }
1807}