use std::ops::Deref;
use pallas_codec::utils::{CborWrap, KeyValuePairs};
use pallas_crypto::hash::Hash;
use pallas_primitives::{
babbage::{
DatumOption, ExUnits as PallasExUnits, NativeScript, NetworkId, PlutusData, PlutusV1Script,
PlutusV2Script, PostAlonzoTransactionOutput, PseudoScript as PallasScript,
PseudoTransactionOutput, Redeemer, RedeemerTag, TransactionBody, TransactionInput,
Tx as BabbageTx, Value, WitnessSet,
},
Fragment,
};
use pallas_traverse::ComputeHash;
use crate::{
transaction::{
model::{
BuilderEra, BuiltTransaction, DatumKind, ExUnits, Output, RedeemerPurpose, ScriptKind,
StagingTransaction,
},
opt_if_empty, Bytes, Bytes32, TransactionStatus,
},
TxBuilderError,
};
pub trait BuildBabbage {
fn build_babbage_raw(self) -> Result<BuiltTransaction, TxBuilderError>;
}
impl BuildBabbage for StagingTransaction {
fn build_babbage_raw(self) -> Result<BuiltTransaction, TxBuilderError> {
let mut inputs = self
.inputs
.unwrap_or_default()
.iter()
.map(|x| TransactionInput {
transaction_id: x.tx_hash.0.into(),
index: x.txo_index,
})
.collect::<Vec<_>>();
inputs.sort_unstable_by_key(|x| (x.transaction_id, x.index));
let outputs = self
.outputs
.unwrap_or_default()
.iter()
.map(babbage_output)
.collect::<Result<Vec<_>, _>>()?;
let mint: Option<KeyValuePairs<Hash<28>, KeyValuePairs<_, _>>> = self.mint.map(|massets| {
massets
.deref()
.iter()
.map(|(pid, assets)| {
(
pid.0.into(),
assets
.iter()
.map(|(n, x)| (n.clone().into(), *x))
.collect::<Vec<_>>()
.into(),
)
})
.collect::<Vec<_>>()
.into()
});
let collateral = self
.collateral_inputs
.unwrap_or_default()
.iter()
.map(|x| TransactionInput {
transaction_id: x.tx_hash.0.into(),
index: x.txo_index,
})
.collect();
let required_signers = self
.disclosed_signers
.unwrap_or_default()
.iter()
.map(|x| x.0.into())
.collect();
let network_id = if let Some(nid) = self.network_id {
match nid {
0 => Some(NetworkId::One),
1 => Some(NetworkId::Two),
_ => return Err(TxBuilderError::InvalidNetworkId),
}
} else {
None
};
let collateral_return = self
.collateral_output
.as_ref()
.map(babbage_output)
.transpose()?;
let reference_inputs = self
.reference_inputs
.unwrap_or_default()
.iter()
.map(|x| TransactionInput {
transaction_id: x.tx_hash.0.into(),
index: x.txo_index,
})
.collect();
let (mut native_script, mut plutus_v1_script, mut plutus_v2_script) =
(vec![], vec![], vec![]);
for (_, script) in self.scripts.unwrap_or_default() {
match script.kind {
ScriptKind::Native => {
let script = NativeScript::decode_fragment(&script.bytes.0)
.map_err(|_| TxBuilderError::MalformedScript)?;
native_script.push(script)
}
ScriptKind::PlutusV1 => {
let script = PlutusV1Script(script.bytes.into());
plutus_v1_script.push(script)
}
ScriptKind::PlutusV2 => {
let script = PlutusV2Script(script.bytes.into());
plutus_v2_script.push(script)
}
}
}
let plutus_data = self
.datums
.unwrap_or_default()
.iter()
.map(|x| {
PlutusData::decode_fragment(x.1.as_ref())
.map_err(|_| TxBuilderError::MalformedDatum)
})
.collect::<Result<Vec<_>, _>>()?;
let mut mint_policies = mint
.clone()
.unwrap_or(vec![].into())
.iter()
.map(|(p, _)| **p)
.collect::<Vec<_>>();
mint_policies.sort_unstable_by_key(|x| *x);
let mut redeemers = vec![];
if let Some(rdmrs) = self.redeemers {
for (purpose, (pd, ex_units)) in rdmrs.deref().iter() {
let ex_units = if let Some(ExUnits { mem, steps }) = ex_units {
PallasExUnits {
mem: *mem,
steps: *steps,
}
} else {
todo!("ExUnits budget calculation not yet implement") };
let data = PlutusData::decode_fragment(pd.as_ref())
.map_err(|_| TxBuilderError::MalformedDatum)?;
match purpose {
RedeemerPurpose::Spend(ref txin) => {
let index = inputs
.iter()
.position(|x| {
(*x.transaction_id, x.index) == (txin.tx_hash.0, txin.txo_index)
})
.ok_or(TxBuilderError::RedeemerTargetMissing)?
as u32;
redeemers.push(Redeemer {
tag: RedeemerTag::Spend,
index,
data,
ex_units,
})
}
RedeemerPurpose::Mint(pid) => {
let index = mint_policies
.iter()
.position(|x| *x == pid.0)
.ok_or(TxBuilderError::RedeemerTargetMissing)?
as u32;
redeemers.push(Redeemer {
tag: RedeemerTag::Mint,
index,
data,
ex_units,
})
} }
}
};
let mut pallas_tx = BabbageTx {
transaction_body: TransactionBody {
inputs,
outputs,
ttl: self.invalid_from_slot,
validity_interval_start: self.valid_from_slot,
fee: self.fee.unwrap_or_default(),
certificates: None, withdrawals: None, update: None, auxiliary_data_hash: None, mint,
script_data_hash: self.script_data_hash.map(|x| x.0.into()),
collateral: opt_if_empty(collateral),
required_signers: opt_if_empty(required_signers),
network_id,
collateral_return,
total_collateral: None, reference_inputs: opt_if_empty(reference_inputs),
},
transaction_witness_set: WitnessSet {
vkeywitness: None,
native_script: opt_if_empty(native_script),
bootstrap_witness: None,
plutus_v1_script: opt_if_empty(plutus_v1_script),
plutus_v2_script: opt_if_empty(plutus_v2_script),
plutus_data: opt_if_empty(plutus_data),
redeemer: opt_if_empty(redeemers),
},
success: true, auxiliary_data: None.into(), };
pallas_tx.transaction_body.auxiliary_data_hash = pallas_tx
.auxiliary_data
.clone()
.map(|ad| ad.compute_hash().to_vec().into())
.into();
Ok(BuiltTransaction {
version: self.version,
era: BuilderEra::Babbage,
status: TransactionStatus::Built,
tx_hash: Bytes32(*pallas_tx.transaction_body.compute_hash()),
tx_bytes: Bytes(pallas_tx.encode_fragment().unwrap()),
signatures: None,
})
}
}
fn babbage_output(
output: &Output,
) -> Result<PseudoTransactionOutput<PostAlonzoTransactionOutput>, TxBuilderError> {
let value = if let Some(ref assets) = output.assets {
let txb_assets = assets
.deref()
.iter()
.map(|(pid, assets)| {
(
pid.0.into(),
assets
.iter()
.map(|(n, x)| (n.clone().into(), *x))
.collect::<Vec<_>>()
.into(),
)
})
.collect::<Vec<_>>()
.into();
Value::Multiasset(output.lovelace, txb_assets)
} else {
Value::Coin(output.lovelace)
};
let datum_option = if let Some(ref d) = output.datum {
match d.kind {
DatumKind::Hash => {
let dh: [u8; 32] = d
.bytes
.as_ref()
.try_into()
.map_err(|_| TxBuilderError::MalformedDatumHash)?;
Some(DatumOption::Hash(dh.into()))
}
DatumKind::Inline => {
let pd = PlutusData::decode_fragment(d.bytes.as_ref())
.map_err(|_| TxBuilderError::MalformedDatum)?;
Some(DatumOption::Data(CborWrap(pd)))
}
}
} else {
None
};
let script_ref = if let Some(ref s) = output.script {
let script = match s.kind {
ScriptKind::Native => PallasScript::NativeScript(
NativeScript::decode_fragment(s.bytes.as_ref())
.map_err(|_| TxBuilderError::MalformedScript)?,
),
ScriptKind::PlutusV1 => {
PallasScript::PlutusV1Script(PlutusV1Script(s.bytes.as_ref().to_vec().into()))
}
ScriptKind::PlutusV2 => {
PallasScript::PlutusV2Script(PlutusV2Script(s.bytes.as_ref().to_vec().into()))
}
};
Some(CborWrap(script))
} else {
None
};
Ok(PseudoTransactionOutput::PostAlonzo(
PostAlonzoTransactionOutput {
address: output.address.to_vec().into(),
value,
datum_option,
script_ref,
},
))
}