1use std::collections::HashMap;
2
3use crate::{
4    PlutusData,
5    ast::{DeBruijn, Program},
6    machine::{cost_model::ExBudget, eval_result::EvalResult},
7};
8use error::Error;
9use pallas_addresses::ScriptHash;
10use pallas_primitives::{
11    Fragment,
12    conway::{
13        CostModels, ExUnits, MintedTx, Redeemer, Redeemers, RedeemersKey, TransactionInput,
14        TransactionOutput,
15    },
16};
17use pallas_traverse::{Era, MultiEraTx};
18pub use phase_one::{eval_phase_one, redeemer_tag_to_string};
19use script_context::PlutusScript;
20pub use script_context::{DataLookupTable, ResolvedInput, SlotConfig};
21
22pub mod error;
23pub mod eval;
24mod phase_one;
25pub mod script_context;
26#[cfg(test)]
27mod tests;
28pub mod to_plutus_data;
29
30pub fn eval_phase_two(
36    tx: &MintedTx,
37    utxos: &[ResolvedInput],
38    cost_mdls: Option<&CostModels>,
39    initial_budget: Option<&ExBudget>,
40    slot_config: &SlotConfig,
41    run_phase_one: bool,
42    with_redeemer: fn(&Redeemer) -> (),
43) -> Result<Vec<(Redeemer, EvalResult)>, Error> {
44    eval_phase_two_with_override(
45        tx,
46        utxos,
47        cost_mdls,
48        initial_budget,
49        slot_config,
50        HashMap::new(),
51        run_phase_one,
52        with_redeemer,
53    )
54}
55
56#[allow(clippy::too_many_arguments)]
58pub fn eval_phase_two_with_override(
59    tx: &MintedTx,
60    utxos: &[ResolvedInput],
61    cost_mdls: Option<&CostModels>,
62    initial_budget: Option<&ExBudget>,
63    slot_config: &SlotConfig,
64    override_scripts: HashMap<ScriptHash, PlutusScript>,
65    run_phase_one: bool,
66    with_redeemer: fn(&Redeemer) -> (),
67) -> Result<Vec<(Redeemer, EvalResult)>, Error> {
68    let redeemers = tx.transaction_witness_set.redeemer.as_ref();
69
70    let mut lookup_table = DataLookupTable::from_transaction(tx, utxos);
71
72    if run_phase_one {
73        eval_phase_one(tx, utxos, &lookup_table)?;
75    }
76
77    override_scripts
79        .into_iter()
80        .for_each(|(hash, script)| lookup_table.override_script(hash, script));
81
82    match redeemers {
83        Some(rs) => {
84            let mut collected_results = vec![];
85
86            let mut remaining_budget = *initial_budget.unwrap_or(&ExBudget::default());
87
88            for (key, data, ex_units) in iter_redeemers(rs) {
89                let redeemer = Redeemer {
90                    tag: key.tag,
91                    index: key.index,
92                    data: data.clone(),
93                    ex_units,
94                };
95
96                with_redeemer(&redeemer);
97
98                let (redeemer, eval_result) = eval::eval_redeemer(
99                    tx,
100                    utxos,
101                    slot_config,
102                    &redeemer,
103                    &lookup_table,
104                    cost_mdls,
105                    &remaining_budget,
106                )?;
107
108                remaining_budget.cpu -= redeemer.ex_units.steps as i64;
111                remaining_budget.mem -= redeemer.ex_units.mem as i64;
112
113                collected_results.push((redeemer, eval_result));
114            }
115
116            Ok(collected_results)
117        }
118        None => Ok(vec![]),
119    }
120}
121
122pub fn eval_phase_two_raw(
127    tx_bytes: &[u8],
128    utxos_bytes: &[(Vec<u8>, Vec<u8>)],
129    cost_mdls_bytes: Option<&[u8]>,
130    initial_budget: (u64, u64),
131    slot_config: (u64, u64, u32),
132    run_phase_one: bool,
133    with_redeemer: fn(&Redeemer) -> (),
134) -> Result<Vec<(Vec<u8>, EvalResult)>, Error> {
135    let multi_era_tx = MultiEraTx::decode_for_era(Era::Conway, tx_bytes)
136        .or_else(|e| MultiEraTx::decode_for_era(Era::Babbage, tx_bytes).map_err(|_| e))
137        .or_else(|e| MultiEraTx::decode_for_era(Era::Alonzo, tx_bytes).map_err(|_| e))?;
138
139    let cost_mdls = cost_mdls_bytes
140        .map(CostModels::decode_fragment)
141        .transpose()?;
142
143    let budget = ExBudget {
144        cpu: initial_budget.0 as i64,
145        mem: initial_budget.1 as i64,
146    };
147
148    let mut utxos = Vec::new();
149
150    for (input, output) in utxos_bytes {
151        utxos.push(ResolvedInput {
152            input: TransactionInput::decode_fragment(input)?,
153            output: TransactionOutput::decode_fragment(output)?,
154        });
155    }
156
157    let sc = SlotConfig {
158        zero_time: slot_config.0,
159        zero_slot: slot_config.1,
160        slot_length: slot_config.2,
161    };
162
163    match multi_era_tx {
164        MultiEraTx::Conway(tx) => {
165            match eval_phase_two(
166                &tx,
167                &utxos,
168                cost_mdls.as_ref(),
169                Some(&budget),
170                &sc,
171                run_phase_one,
172                with_redeemer,
173            ) {
174                Ok(redeemers) => Ok(redeemers
175                    .into_iter()
176                    .map(|(r, e)| (r.encode_fragment().unwrap(), e))
177                    .collect()),
178                Err(err) => Err(err),
179            }
180        }
181        _ => unimplemented!(
182            r#"The transaction is serialized in an old era format. Because we're slightly lazy to
183maintain backward compatibility with every possible transaction format AND, because
184those formats are mostly forward-compatible, you are kindly expected to provide a
185transaction in a format suitable for the Conway era."#
186        ),
187    }
188}
189
190pub fn apply_params_to_script(
191    params_bytes: &[u8], plutus_script_bytes: &[u8],
193) -> Result<Vec<u8>, Error> {
194    let params = match PlutusData::decode_fragment(params_bytes).unwrap() {
195        PlutusData::Array(res) => res,
196        _ => unreachable!(),
197    };
198
199    let mut buffer = Vec::new();
200    let mut program = Program::<DeBruijn>::from_cbor(plutus_script_bytes, &mut buffer)?;
201
202    for param in params.to_vec() {
203        program = program.apply_data(param);
204    }
205
206    match program.to_cbor() {
207        Ok(res) => Ok(res),
208        Err(_) => Err(Error::ApplyParamsError),
209    }
210}
211
212pub fn iter_redeemers(
213    redeemers: &Redeemers,
214) -> impl Iterator<Item = (RedeemersKey, &PlutusData, ExUnits)> {
215    match redeemers {
216        Redeemers::List(rs) => Box::new(rs.iter().map(|r| {
217            (
218                RedeemersKey {
219                    tag: r.tag,
220                    index: r.index,
221                },
222                &r.data,
223                r.ex_units,
224            )
225        })),
226        Redeemers::Map(kv) => Box::new(kv.iter().map(|(k, v)| {
227            (
228                RedeemersKey {
229                    tag: k.tag,
230                    index: k.index,
231                },
232                &v.data,
233                v.ex_units,
234            )
235        }))
236            as Box<dyn Iterator<Item = (RedeemersKey, &PlutusData, ExUnits)>>,
237    }
238}