whisky_provider/blockfrost/
evaluator.rs

1use async_trait::async_trait;
2use whisky_csl::CSLParser;
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],
60        additional_txs: &[String],
61        _network: &Network,
62        _slot_config: &SlotConfig,
63    ) -> Result<Vec<Action>, WError> {
64        let mut tx_out_cbors = Vec::new();
65
66        for tx_str in additional_txs {
67            let utxos = CSLParser::extract_output_utxos(tx_str).map_err(|e| {
68                WError::new("evaluate_tx", &format!("Failed to get output UTXOs: {}", e))
69            })?;
70
71            for (index, utxo) in utxos.iter().enumerate() {
72                let additional_utxo = AdditionalUtxo {
73                    tx_hash: utxo.input.tx_hash.to_string(),
74                    index: index as u32,
75                    address: utxo.output.address.to_string(),
76                    value: utxo.output.amount.clone(),
77                };
78                tx_out_cbors.push(additional_utxo.to_ogmios());
79            }
80        }
81
82        let url = "/utils/txs/evaluate/utxos";
83        let body = EvaluateTx {
84            cbor: tx.to_string(),
85            additional_utxo_set: tx_out_cbors,
86        };
87
88        let resp = self
89            .blockfrost_client
90            .post(url, &body)
91            .await
92            .map_err(WError::from_err("Blockfrost - evaluate_tx"))?;
93
94        let parsed_json: serde_json::Value = serde_json::from_str(&resp)
95            .map_err(WError::from_err("Blockfrost - evaluate_tx type error"))?;
96
97        println!("parsed_json: {:?}", parsed_json);
98
99        let evaluation_result = parsed_json
100            .get("result")
101            .ok_or_else(WError::from_opt(
102                "Blockfrost - evaluate_tx",
103                "failed to get EvaluationResult from resp",
104            ))?
105            .get("EvaluationResult")
106            .ok_or_else(WError::from_opt(
107                "Blockfrost - evaluate_tx",
108                "failed to get EvaluationResult from resp",
109            ))?;
110
111        let evaluation_map: HashMap<String, OgmiosBudget> =
112            serde_json::from_value(evaluation_result.clone())
113                .map_err(WError::from_err("Blockfrost - evaluate_tx type error"))?;
114
115        println!("Blockfrost evaluate_tx response: {}", resp);
116
117        let actions: Result<Vec<Action>, WError> = evaluation_map
118            .into_iter()
119            .map(|(key, budget)| {
120                // Parse the key to extract the tag and index
121                let parts: Vec<&str> = key.split(':').collect();
122                if parts.len() != 2 {
123                    return Err(WError::new(
124                        "evaluate_tx",
125                        &format!("Invalid key format: {}", key),
126                    ));
127                }
128                let tag = match parts[0] {
129                    "spend" => RedeemerTag::Spend,
130                    "mint" => RedeemerTag::Mint,
131                    "cert" => RedeemerTag::Cert,
132                    "reward" => RedeemerTag::Reward,
133                    "vote" => RedeemerTag::Vote,
134                    "propose" => RedeemerTag::Propose,
135                    _ => {
136                        return Err(WError::new(
137                            "evaluate_tx",
138                            &format!("Unknown tag: {}", parts[0]),
139                        ))
140                    }
141                };
142                let index = parts[1]
143                    .parse::<u32>()
144                    .map_err(|e| WError::new("evaluate_tx", &format!("Invalid index: {}", e)))?;
145
146                // Create an Action
147                Ok(Action {
148                    index,
149                    budget: Budget {
150                        mem: budget.memory,
151                        steps: budget.steps,
152                    },
153                    tag,
154                })
155            })
156            .collect();
157        actions
158    }
159}
160
161#[test]
162fn test_additional_utxo_to_ogmios() {
163    let utxo = AdditionalUtxo {
164        tx_hash: "hash".to_string(),
165        index: 0,
166        address: "addr1".to_string(),
167        value: vec![
168            Asset::new_from_str("lovelace", "1000000"),
169            Asset::new_from_str("asset1", "5"),
170        ],
171    };
172    let ogmios_value = utxo.to_ogmios();
173    let expected_json = r#"[
174        {
175            "transaction": {"id": "hash"},
176            "output": {"index": 0}
177        },
178        {
179            "address": "addr1",
180            "value": {
181                "lovelace": 1000000,
182                "asset1": 5
183            }
184        }
185    ]"#;
186
187    let expected_value: serde_json::Value = serde_json::from_str(expected_json).unwrap();
188    let actual_value: serde_json::Value = ogmios_value;
189
190    assert_eq!(actual_value, expected_value);
191}