1use crate::converter::bytes_from_bech32;
2use crate::tx_parser;
3
4use super::phase_two::{eval_phase_two, PhaseTwoEvalResult};
5use pallas::ledger::primitives::conway::CostModels;
6use pallas::ledger::primitives::conway::Tx;
7use pallas::ledger::primitives::Fragment;
8use pallas_codec::utils::NonEmptyKeyValuePairs;
9use pallas_codec::utils::{Bytes, CborWrap, PositiveCoin};
10use pallas_primitives::conway::Redeemer;
11use pallas_primitives::conway::{
12 AssetName, Coin, DatumOption, PlutusData, PolicyId, PostAlonzoTransactionOutput,
13 RedeemerTag as PallasRedeemerTag, ScriptRef, TransactionOutput, Value,
14};
15use std::collections::BTreeMap;
16use std::collections::HashMap;
17use std::str::FromStr;
18use uplc::tx::SlotConfig;
19use uplc::Fragment as UplcFragment;
20use uplc::{tx::error::Error as UplcError, tx::ResolvedInput, Hash, TransactionInput};
21use whisky_common::*;
22
23#[derive(serde::Deserialize, serde::Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct JsonSlotConfig {
26 pub slot_length: u32,
27 pub zero_slot: u64,
28 pub zero_time: u64,
29}
30
31pub fn evaluate_tx_scripts(
32 tx_hex: &str,
33 inputs: &[UTxO],
34 additional_txs: &[String],
35 network: &Network,
36 slot_config: &SlotConfig,
37) -> Result<Vec<EvalResult>, WError> {
38 let tx_bytes = hex::decode(tx_hex).expect("Invalid tx hex");
39 let tx = Tx::decode_fragment(&tx_bytes)
40 .map_err(WError::from_err("evaluate_tx_scripts - tx decode"))?;
41
42 let mut all_utxos = inputs.to_vec();
43 for additional_tx in additional_txs {
44 let additional_utxos = tx_parser::extract_tx_utxos(additional_tx)
45 .map_err(WError::from_err("evaluate_tx_scripts - extract_tx_utxos"))?;
46 all_utxos.extend(additional_utxos)
47 }
48
49 let all_inputs: Vec<UTxO> = inputs.to_vec();
50
51 eval_phase_two(
52 &tx,
53 &to_pallas_utxos(&all_inputs)?,
54 Some(&get_cost_mdls(network)?),
55 slot_config,
56 )
57 .map_err(|err| {
58 WError::new(
59 "evaluate_tx_scripts",
60 &format!("Error occurred during evaluation: {}", err),
61 )
62 })
63 .map(|reds| reds.into_iter().map(map_eval_result).collect())
64}
65
66pub fn map_eval_result(result: PhaseTwoEvalResult) -> EvalResult {
67 match result {
68 PhaseTwoEvalResult::Success(redeemer) => EvalResult::Success(Action {
69 index: redeemer.index,
70 budget: Budget {
71 mem: redeemer.ex_units.mem,
72 steps: redeemer.ex_units.steps,
73 },
74 tag: map_redeemer_tag(&redeemer.tag),
75 }),
76 PhaseTwoEvalResult::Error(redeemer, err) => {
77 EvalResult::Error(map_error_to_eval_error(err, redeemer))
78 }
79 }
80}
81
82pub fn map_error_to_eval_error(err: UplcError, original_redeemer: Redeemer) -> EvalError {
83 match err {
84 UplcError::Machine(err, budget, logs) => EvalError {
85 index: original_redeemer.index,
86 budget: Budget {
87 mem: budget.mem as u64,
88 steps: budget.cpu as u64,
89 },
90 tag: map_redeemer_tag(&original_redeemer.tag),
91 error_message: format!("{}", err),
92 logs,
93 },
94 UplcError::RedeemerError { err, .. } => match *err {
95 UplcError::Machine(err, budget, logs) => EvalError {
96 index: original_redeemer.index,
97 budget: Budget {
98 mem: budget.mem as u64,
99 steps: budget.cpu as u64,
100 },
101 tag: map_redeemer_tag(&original_redeemer.tag),
102 error_message: format!("{}", err),
103 logs,
104 },
105 _ => EvalError {
106 index: original_redeemer.index,
107 budget: Budget { mem: 0, steps: 0 },
108 tag: map_redeemer_tag(&original_redeemer.tag),
109 error_message: format!("{}", err),
110 logs: vec![],
111 },
112 },
113 _ => EvalError {
114 index: original_redeemer.index,
115 budget: Budget { mem: 0, steps: 0 },
116 tag: map_redeemer_tag(&original_redeemer.tag),
117 error_message: format!("{}", err),
118 logs: vec![],
119 },
120 }
121}
122
123pub fn map_redeemer_to_action(redeemer: Redeemer) -> Action {
124 Action {
125 index: redeemer.index,
126 budget: Budget {
127 mem: redeemer.ex_units.mem,
128 steps: redeemer.ex_units.steps,
129 },
130 tag: map_redeemer_tag(&redeemer.tag),
131 }
132}
133
134pub fn map_redeemer_tag(tag: &PallasRedeemerTag) -> RedeemerTag {
135 match tag {
136 PallasRedeemerTag::Spend => RedeemerTag::Spend,
137 PallasRedeemerTag::Mint => RedeemerTag::Mint,
138 PallasRedeemerTag::Cert => RedeemerTag::Cert,
139 PallasRedeemerTag::Reward => RedeemerTag::Reward,
140 PallasRedeemerTag::Vote => RedeemerTag::Vote,
141 PallasRedeemerTag::Propose => RedeemerTag::Propose,
142 }
143}
144
145pub fn get_cost_mdls(network: &Network) -> Result<CostModels, WError> {
146 let cost_model_list = get_cost_models_from_network(network);
147 if cost_model_list.len() < 3 {
148 return Err(WError::new(
149 "get_cost_mdls",
150 "Cost models have to contain at least PlutusV1, PlutusV2, and PlutusV3 costs",
151 ));
152 };
153 Ok(CostModels {
154 plutus_v1: Some(cost_model_list[0].clone()),
155 plutus_v2: Some(cost_model_list[1].clone()),
156 plutus_v3: Some(cost_model_list[2].clone()),
157 unknown: BTreeMap::new(),
158 })
159}
160
161pub fn to_pallas_utxos(utxos: &Vec<UTxO>) -> Result<Vec<ResolvedInput>, WError> {
162 let mut resolved_inputs = Vec::new();
163 for utxo in utxos {
164 let tx_hash: [u8; 32] = hex::decode(&utxo.input.tx_hash)
165 .map_err(|err| {
166 WError::new(
167 "to_pallas_utxos",
168 &format!("Invalid tx hash found: {}", err),
169 )
170 })?
171 .try_into()
172 .map_err(|_e| WError::new("to_pallas_utxos", "Invalid tx hash length found"))?;
173
174 let resolved_input = ResolvedInput {
175 input: TransactionInput {
176 transaction_id: Hash::from(tx_hash),
177 index: utxo.input.output_index.into(),
178 },
179 output: TransactionOutput::PostAlonzo(PostAlonzoTransactionOutput {
180 address: Bytes::from_str(&bytes_from_bech32(&utxo.output.address)?).map_err(
181 |err| {
182 WError::new(
183 "to_pallas_utxos",
184 &format!("Invalid address found: {}", err),
185 )
186 },
187 )?,
188 value: to_pallas_value(&utxo.output.amount)
189 .map_err(WError::add_err_trace("to_pallas_utxos"))?,
190 datum_option: to_pallas_datum(&utxo.output)
191 .map_err(WError::add_err_trace("to_pallas_utxos"))?,
192 script_ref: to_pallas_script_ref(&utxo.output.script_ref)
193 .map_err(WError::add_err_trace("to_pallas_utxos"))?,
194 }),
195 };
196 resolved_inputs.push(resolved_input);
197 }
198 Ok(resolved_inputs)
199}
200
201pub fn to_pallas_script_ref(
202 script_ref: &Option<String>,
203) -> Result<Option<CborWrap<ScriptRef>>, WError> {
204 if let Some(script_ref) = script_ref {
205 let script_bytes = hex::decode(script_ref).map_err(WError::from_err(
206 "to_pallas_script_ref - Invalid script ref hex",
207 ))?;
208
209 let pallas_script = ScriptRef::decode_fragment(&script_bytes).map_err(WError::from_err(
210 "to_pallas_script_ref - Invalid script ref bytes",
211 ))?;
212
213 Ok(Some(CborWrap(pallas_script)))
214 } else {
215 Ok(None)
216 }
217}
218
219pub fn to_pallas_datum(utxo_output: &UtxoOutput) -> Result<Option<DatumOption>, WError> {
220 if let Some(inline_datum) = &utxo_output.plutus_data {
221 let plutus_data_bytes = hex::decode(inline_datum).map_err(WError::from_err(
223 "to_pallas_datum - Invalid plutus data hex",
224 ))?;
225 println!("utxo_output: {:?}", utxo_output);
226 let datum = CborWrap(PlutusData::decode_fragment(&plutus_data_bytes).map_err(
227 WError::from_err("to_pallas_datum - Invalid plutus data bytes"),
228 )?);
229 Ok(Some(DatumOption::Data(datum)))
230 } else if let Some(datum_hash) = &utxo_output.data_hash {
231 let datum_hash_bytes: [u8; 32] = hex::decode(datum_hash)
232 .map_err(WError::from_err("to_pallas_datum - Invalid datum hash hex"))?
233 .try_into()
234 .map_err(|_e| {
235 WError::new("to_pallas_datum", "Invalid byte length of datum hash found")
236 })?;
237 Ok(Some(DatumOption::Hash(Hash::from(datum_hash_bytes))))
238 } else {
239 Ok(None)
240 }
241}
242
243pub fn to_pallas_value(assets: &Vec<Asset>) -> Result<Value, WError> {
244 if assets.len() == 1 {
245 match assets[0].unit().as_str() {
246 "lovelace" => Ok(Value::Coin(assets[0].quantity().parse::<u64>().unwrap())),
247 _ => Err(WError::new("to_pallas_value", "Invalid value")),
248 }
249 } else {
250 to_pallas_multi_asset_value(assets)
251 }
252}
253
254pub fn to_pallas_multi_asset_value(assets: &Vec<Asset>) -> Result<Value, WError> {
255 let mut coins: Coin = 0;
256 let mut asset_mapping: HashMap<String, Vec<(String, String)>> = HashMap::new();
257 for asset in assets {
258 if asset.unit() == "lovelace" || asset.unit().is_empty() {
259 coins = asset.quantity().parse::<u64>().unwrap();
260 } else {
261 let asset_unit = asset.unit();
262 let (policy_id, asset_name) = asset_unit.split_at(56);
263 asset_mapping
264 .entry(policy_id.to_string())
265 .or_default()
266 .push((asset_name.to_string(), asset.quantity().clone()))
267 }
268 }
269
270 let mut multi_asset = Vec::new();
271 for (policy_id, asset_list) in &asset_mapping {
272 let policy_id_bytes: [u8; 28] = hex::decode(policy_id)
273 .map_err(WError::from_err(
274 "to_pallas_multi_asset_value - Invalid policy id hex",
275 ))?
276 .try_into()
277 .map_err(|_e| {
278 WError::new(
279 "to_pallas_multi_asset_vale",
280 "Invalid length policy id found",
281 )
282 })?;
283
284 let policy_id = PolicyId::from(policy_id_bytes);
285 let mut mapped_assets = Vec::new();
286 for asset in asset_list {
287 let (asset_name, asset_quantity) = asset;
288 let asset_name_bytes = AssetName::from(hex::decode(asset_name).map_err(
289 WError::from_err("to_pallas_multi_asset_value - Invalid asset name hex"),
290 )?);
291 mapped_assets.push((
292 asset_name_bytes,
293 PositiveCoin::try_from(asset_quantity.parse::<u64>().unwrap()).unwrap(),
294 ));
295 }
296 multi_asset.push((policy_id, NonEmptyKeyValuePairs::Def(mapped_assets)));
297 }
298 let pallas_multi_asset = pallas_codec::utils::NonEmptyKeyValuePairs::Def(multi_asset);
299 Ok(Value::Multiasset(coins, pallas_multi_asset))
300}