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