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)
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 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 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}