whisky_provider/blockfrost/
evaluator.rs1use 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 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 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}