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