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 mut all_utxos = inputs.to_vec();
54 for additional_tx in additional_txs {
55 let additional_utxos = CSLParser::extract_output_utxos(additional_tx)
56 .map_err(
57 WError::from_err("evaluate_tx_scripts - extract_output_utxos"),
58 )?;
59 all_utxos.extend(additional_utxos)
60 }
61
62 let all_inputs: Vec<UTxO> = inputs.to_vec();
63
64 eval_phase_two(
65 &tx,
66 &to_pallas_utxos(&all_inputs)?,
67 Some(&get_cost_mdls(network)?),
68 slot_config,
69 )
70 .map_err(|err| {
71 WError::new(
72 "evaluate_tx_scripts",
73 &format!("Error occurred during evaluation: {}", err),
74 )
75 })
76 .map(|reds| reds.into_iter().map(map_eval_result).collect())
77}
78
79pub fn map_eval_result(result: PhaseTwoEvalResult) -> EvalResult {
80 match result {
81 PhaseTwoEvalResult::Success(redeemer) => {
82 EvalResult::Success(map_redeemer_to_action(redeemer))
83 }
84 PhaseTwoEvalResult::Error(redeemer, err) => {
85 EvalResult::Error(map_error_to_eval_error(err, redeemer))
86 }
87 }
88}
89
90pub fn map_error_to_eval_error(err: UplcError, original_redeemer: Redeemer) -> EvalError {
91 match err {
92 UplcError::Machine(err, budget, logs) => EvalError {
93 index: original_redeemer.index,
94 budget: Budget {
95 mem: budget.mem as u64,
96 steps: budget.cpu as u64,
97 },
98 tag: map_redeemer_tag(&original_redeemer.tag),
99 error_message: format!("{}", err),
100 logs,
101 },
102 UplcError::RedeemerError { err, .. } => match *err {
103 UplcError::Machine(err, budget, logs) => EvalError {
104 index: original_redeemer.index,
105 budget: Budget {
106 mem: budget.mem as u64,
107 steps: budget.cpu as u64,
108 },
109 tag: map_redeemer_tag(&original_redeemer.tag),
110 error_message: format!("{}", err),
111 logs,
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 _ => EvalError {
122 index: original_redeemer.index,
123 budget: Budget { mem: 0, steps: 0 },
124 tag: map_redeemer_tag(&original_redeemer.tag),
125 error_message: format!("{}", err),
126 logs: vec![],
127 },
128 }
129}
130
131pub fn map_redeemer_to_action(redeemer: Redeemer) -> Action {
132 Action {
133 index: redeemer.index,
134 budget: Budget {
135 mem: redeemer.ex_units.mem,
136 steps: redeemer.ex_units.steps,
137 },
138 tag: map_redeemer_tag(&redeemer.tag),
139 }
140}
141
142pub fn map_redeemer_tag(tag: &PRedeemerTag) -> RedeemerTag {
143 match tag {
144 PRedeemerTag::Spend => RedeemerTag::Spend,
145 PRedeemerTag::Mint => RedeemerTag::Mint,
146 PRedeemerTag::Cert => RedeemerTag::Cert,
147 PRedeemerTag::Reward => RedeemerTag::Reward,
148 PRedeemerTag::Vote => RedeemerTag::Vote,
149 PRedeemerTag::Propose => RedeemerTag::Propose,
150 }
151}
152
153pub fn get_cost_mdls(network: &Network) -> Result<CostModels, WError> {
154 let cost_model_list = get_cost_models_from_network(network);
155 if cost_model_list.len() < 3 {
156 return Err(WError::new(
157 "get_cost_mdls",
158 "Cost models have to contain at least PlutusV1, PlutusV2, and PlutusV3 costs",
159 ));
160 };
161 Ok(CostModels {
162 plutus_v1: Some(cost_model_list[0].clone()),
163 plutus_v2: Some(cost_model_list[1].clone()),
164 plutus_v3: Some(cost_model_list[2].clone()),
165 })
166}
167
168pub fn to_pallas_utxos(utxos: &Vec<UTxO>) -> Result<Vec<ResolvedInput>, WError> {
169 let mut resolved_inputs = Vec::new();
170 for utxo in utxos {
171 let tx_hash: [u8; 32] = hex::decode(&utxo.input.tx_hash)
172 .map_err(|err| {
173 WError::new(
174 "to_pallas_utxos",
175 &format!("Invalid tx hash found: {}", err),
176 )
177 })?
178 .try_into()
179 .map_err(|_e| WError::new("to_pallas_utxos", "Invalid tx hash length found"))?;
180
181 let resolved_input = ResolvedInput {
182 input: TransactionInput {
183 transaction_id: Hash::from(tx_hash),
184 index: utxo.input.output_index.into(),
185 },
186 output: TransactionOutput::PostAlonzo(PostAlonzoTransactionOutput {
187 address: Bytes::from(
188 csl::Address::from_bech32(&utxo.output.address)
189 .map_err(|err| {
190 WError::new(
191 "to_pallas_utxos",
192 &format!("Invalid address found: {:?}", err),
193 )
194 })?
195 .to_bytes(),
196 ),
197 value: to_pallas_value(&utxo.output.amount)
198 .map_err(WError::add_err_trace("to_pallas_utxos"))?,
199 datum_option: to_pallas_datum(&utxo.output)
200 .map_err(WError::add_err_trace("to_pallas_utxos"))?,
201 script_ref: to_pallas_script_ref(&utxo.output.script_ref)
202 .map_err(WError::add_err_trace("to_pallas_utxos"))?,
203 }),
204 };
205 resolved_inputs.push(resolved_input);
206 }
207 Ok(resolved_inputs)
208}
209
210pub fn to_pallas_script_ref(
211 script_ref: &Option<String>,
212) -> Result<Option<CborWrap<ScriptRef>>, WError> {
213 if let Some(script_ref) = script_ref {
214 let script_bytes = hex::decode(script_ref).map_err(WError::from_err(
215 "to_pallas_script_ref - Invalid script ref hex",
216 ))?;
217
218 let pallas_script = ScriptRef::decode_fragment(&script_bytes).map_err(WError::from_err(
219 "to_pallas_script_ref - Invalid script ref bytes",
220 ))?;
221
222 Ok(Some(CborWrap(pallas_script)))
223 } else {
224 Ok(None)
225 }
226}
227
228pub fn to_pallas_datum(utxo_output: &UtxoOutput) -> Result<Option<DatumOption>, WError> {
229 if let Some(inline_datum) = &utxo_output.plutus_data {
230 let plutus_data_bytes = hex::decode(inline_datum).map_err(WError::from_err(
232 "to_pallas_datum - Invalid plutus data hex",
233 ))?;
234 let datum = CborWrap(PlutusData::decode_fragment(&plutus_data_bytes).map_err(
235 WError::from_err("to_pallas_datum - Invalid plutus data bytes"),
236 )?);
237 Ok(Some(DatumOption::Data(datum)))
238 } else if let Some(datum_hash) = &utxo_output.data_hash {
239 let datum_hash_bytes: [u8; 32] = hex::decode(datum_hash)
240 .map_err(WError::from_err("to_pallas_datum - Invalid datum hash hex"))?
241 .try_into()
242 .map_err(|_e| {
243 WError::new("to_pallas_datum", "Invalid byte length of datum hash found")
244 })?;
245 Ok(Some(DatumOption::Hash(Hash::from(datum_hash_bytes))))
246 } else {
247 Ok(None)
248 }
249}
250
251pub fn to_pallas_value(assets: &Vec<Asset>) -> Result<Value, WError> {
252 if assets.len() == 1 {
253 match assets[0].unit().as_str() {
254 "lovelace" => Ok(Value::Coin(assets[0].quantity().parse::<u64>().unwrap())),
255 _ => Err(WError::new("to_pallas_value", "Invalid value")),
256 }
257 } else {
258 to_pallas_multi_asset_value(assets)
259 }
260}
261
262pub fn to_pallas_multi_asset_value(assets: &Vec<Asset>) -> Result<Value, WError> {
263 let mut coins: Coin = 0;
264 let mut asset_mapping: HashMap<String, Vec<(String, String)>> = HashMap::new();
265 for asset in assets {
266 if asset.unit() == "lovelace" || asset.unit().is_empty() {
267 coins = asset.quantity().parse::<u64>().unwrap();
268 } else {
269 let asset_unit = asset.unit();
270 let (policy_id, asset_name) = asset_unit.split_at(56);
271 asset_mapping
272 .entry(policy_id.to_string())
273 .or_default()
274 .push((asset_name.to_string(), asset.quantity().clone()))
275 }
276 }
277
278 let mut multi_asset = Vec::new();
279 for (policy_id, asset_list) in &asset_mapping {
280 let policy_id_bytes: [u8; 28] = hex::decode(policy_id)
281 .map_err(WError::from_err(
282 "to_pallas_multi_asset_value - Invalid policy id hex",
283 ))?
284 .try_into()
285 .map_err(|_e| {
286 WError::new(
287 "to_pallas_multi_asset_vale",
288 "Invalid length policy id found",
289 )
290 })?;
291
292 let policy_id = PolicyId::from(policy_id_bytes);
293 let mut mapped_assets = Vec::new();
294 for asset in asset_list {
295 let (asset_name, asset_quantity) = asset;
296 let asset_name_bytes = AssetName::from(hex::decode(asset_name).map_err(
297 WError::from_err("to_pallas_multi_asset_value - Invalid asset name hex"),
298 )?);
299 mapped_assets.push((
300 asset_name_bytes,
301 PositiveCoin::try_from(asset_quantity.parse::<u64>().unwrap()).unwrap(),
302 ));
303 }
304 multi_asset.push((policy_id, NonEmptyKeyValuePairs::Def(mapped_assets)));
305 }
306 let pallas_multi_asset = pallas_codec::utils::NonEmptyKeyValuePairs::Def(multi_asset);
307 Ok(Value::Multiasset(coins, pallas_multi_asset))
308}