whisky_provider/blockfrost/
evaluator.rs

1use async_trait::async_trait;
2use whisky_csl::TxParser;
3
4use uplc::tx::SlotConfig;
5use whisky_common::models::{Network, UTxO};
6
7use whisky_common::*;
8
9use super::*;
10
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Serialize, Debug)]
15pub struct AdditionalUtxo {
16    tx_hash: String,
17    index: u32,
18    address: String,
19    value: Vec<Asset>,
20}
21
22impl AdditionalUtxo {
23    pub fn to_ogmios(&self) -> serde_json::Value {
24        let mut value: HashMap<String, u64> = HashMap::new();
25        self.value.iter().for_each(|asset| {
26            value.insert(asset.unit().clone(), asset.quantity_i128() as u64);
27        });
28
29        serde_json::json!([
30            {
31                "transaction": {"id": self.tx_hash},
32                "output": {"index": self.index},
33            },
34            {
35                "address": self.address,
36                "value": value
37            }
38        ])
39    }
40}
41
42#[derive(Serialize)]
43#[serde(rename_all = "camelCase")]
44pub struct EvaluateTx {
45    cbor: String,
46    additional_utxo_set: Vec<serde_json::Value>,
47}
48#[derive(Serialize, Deserialize)]
49pub struct OgmiosBudget {
50    pub memory: u64,
51    pub steps: u64,
52}
53
54#[async_trait]
55impl Evaluator for BlockfrostProvider {
56    async fn evaluate_tx(
57        &self,
58        tx: &str,
59        _inputs: &[UTxO], // TODO: parse this also into additional_txs
60        additional_txs: &[String],
61        _network: &Network,
62        _slot_config: &SlotConfig,
63    ) -> Result<Vec<Action>, WError> {
64        let tx_out_cbors: Vec<serde_json::Value> = additional_txs
65            .iter()
66            .flat_map(|tx| {
67                let parsed_tx = TxParser::new(tx);
68                parsed_tx
69                    .unwrap() //TODO: add error handling
70                    .get_tx_outs_utxo()
71                    .unwrap() // TODO: add error handling
72                    .iter()
73                    .enumerate()
74                    .map(|(index, utxo)| {
75                        let additional_utxo = AdditionalUtxo {
76                            tx_hash: utxo.input.tx_hash.to_string(), // TODO: add error handling
77                            index: index as u32,                     // Use the index here
78                            address: utxo.output.address.to_string(),
79                            value: utxo.output.amount.clone(),
80                        };
81                        additional_utxo.to_ogmios()
82                    })
83                    .collect::<Vec<serde_json::Value>>()
84            })
85            .collect();
86
87        let url = "/utils/txs/evaluate/utxos";
88        let body = EvaluateTx {
89            cbor: tx.to_string(),
90            additional_utxo_set: tx_out_cbors,
91        };
92
93        let resp = self
94            .blockfrost_client
95            .post(url, &body)
96            .await
97            .map_err(WError::from_err("Blockfrost - evaluate_tx"))?;
98
99        let parsed_json: serde_json::Value = serde_json::from_str(&resp)
100            .map_err(WError::from_err("Blockfrost - evaluate_tx type error"))?;
101
102        println!("parsed_json: {:?}", parsed_json);
103
104        let evaluation_result = parsed_json
105            .get("result")
106            .ok_or_else(WError::from_opt(
107                "Blockfrost - evaluate_tx",
108                "failed to get EvaluationResult from resp",
109            ))?
110            .get("EvaluationResult")
111            .ok_or_else(WError::from_opt(
112                "Blockfrost - evaluate_tx",
113                "failed to get EvaluationResult from resp",
114            ))?;
115
116        let evaluation_map: HashMap<String, OgmiosBudget> =
117            serde_json::from_value(evaluation_result.clone())
118                .map_err(WError::from_err("Blockfrost - evaluate_tx type error"))?;
119
120        println!("Blockfrost evaluate_tx response: {}", resp);
121
122        let actions = evaluation_map
123            .into_iter()
124            .map(|(key, budget)| {
125                // Parse the key to extract the tag and index
126                let parts: Vec<&str> = key.split(':').collect();
127                let tag = match parts[0] {
128                    "spend" => RedeemerTag::Spend,
129                    "mint" => RedeemerTag::Mint,
130                    "cert" => RedeemerTag::Cert,
131                    "reward" => RedeemerTag::Reward,
132                    "vote" => RedeemerTag::Vote,
133                    "propose" => RedeemerTag::Propose,
134                    _ => panic!("Unknown tag: {}", parts[0]),
135                };
136                let index = parts[1].parse::<u32>().expect("Invalid index");
137
138                // Create an Action
139                Action {
140                    index,
141                    budget: Budget {
142                        mem: budget.memory,
143                        steps: budget.steps,
144                    },
145                    tag,
146                }
147            })
148            .collect();
149        Ok(actions)
150    }
151}
152
153#[test]
154fn test_additional_utxo_to_ogmios() {
155    let utxo = AdditionalUtxo {
156        tx_hash: "hash".to_string(),
157        index: 0,
158        address: "addr1".to_string(),
159        value: vec![
160            Asset::new_from_str("lovelace", "1000000"),
161            Asset::new_from_str("asset1", "5"),
162        ],
163    };
164    let ogmios_value = utxo.to_ogmios();
165    let expected_json = r#"[
166        {
167            "transaction": {"id": "hash"},
168            "output": {"index": 0}
169        },
170        {
171            "address": "addr1",
172            "value": {
173                "lovelace": 1000000,
174                "asset1": 5
175            }
176        }
177    ]"#;
178
179    let expected_value: serde_json::Value = serde_json::from_str(expected_json).unwrap();
180    let actual_value: serde_json::Value = ogmios_value;
181
182    assert_eq!(actual_value, expected_value);
183}