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], 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() .get_tx_outs_utxo()
71 .unwrap() .iter()
73 .enumerate()
74 .map(|(index, utxo)| {
75 let additional_utxo = AdditionalUtxo {
76 tx_hash: utxo.input.tx_hash.to_string(), index: index as u32, 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 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 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}