1use crate::csl::{
2 CostStore, Costs, InputsBuilderExt, NetworkTypeExt, TransactionBuilderExt, TransactionExt,
3 empty_asset_name, get_builder_config, unit_plutus_data,
4};
5use crate::governance::GovernanceData;
6use crate::multisig::MultiSigSmartContractResult::TransactionSubmitted;
7use crate::multisig::submit_or_create_tx_to_sign;
8use crate::plutus_script::PlutusScript;
9use crate::{
10 await_tx::AwaitTx, cardano_keys::CardanoPaymentSigningKey, csl::TransactionContext,
11 multisig::MultiSigSmartContractResult,
12};
13use anyhow::anyhow;
14use cardano_serialization_lib::{
15 Int, PlutusData, Transaction, TransactionBuilder, TxInputsBuilder,
16};
17use ogmios_client::{
18 query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
19 query_network::QueryNetwork,
20 transactions::Transactions,
21 types::OgmiosUtxo,
22};
23use partner_chains_plutus_data::governed_map::{
24 GovernedMapDatum, governed_map_datum_to_plutus_data,
25};
26use sidechain_domain::byte_string::ByteString;
27use sidechain_domain::{PolicyId, UtxoId};
28use std::ops::Neg;
29
30#[cfg(test)]
31mod tests;
32
33pub async fn run_insert<
37 C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
38 A: AwaitTx,
39>(
40 genesis_utxo: UtxoId,
41 key: String,
42 value: ByteString,
43 payment_signing_key: &CardanoPaymentSigningKey,
44 ogmios_client: &C,
45 await_tx: &A,
46) -> anyhow::Result<Option<MultiSigSmartContractResult>> {
47 let ctx = TransactionContext::for_payment_key(payment_signing_key, ogmios_client).await?;
48 let scripts = crate::scripts_data::governed_map_scripts(genesis_utxo, ctx.network)?;
49 let validator_utxos = ogmios_client.query_utxos(&[scripts.validator_address.clone()]).await?;
50
51 let tx_hash_opt = match get_current_value(validator_utxos, key.clone(), scripts.policy_id()) {
52 Some(current_value) if current_value != value => {
53 return Err(anyhow!("There is already a value stored for key '{key}'."));
54 },
55 Some(_current_value) => {
56 log::info!("Value for key '{key}' is already set to the same value. Skipping insert.");
57 None
58 },
59 None => {
60 log::info!("There is no value stored for key '{key}'. Inserting new one.");
61 Some(
62 insert(
63 &scripts.validator,
64 &scripts.policy,
65 key,
66 value,
67 ctx,
68 genesis_utxo,
69 ogmios_client,
70 await_tx,
71 )
72 .await?,
73 )
74 },
75 };
76 if let Some(TransactionSubmitted(tx_hash)) = tx_hash_opt {
77 await_tx.await_tx_output(ogmios_client, UtxoId::new(tx_hash.0, 0)).await?;
78 }
79 Ok(tx_hash_opt)
80}
81
82async fn insert<
83 C: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
84 A: AwaitTx,
85>(
86 validator: &PlutusScript,
87 policy: &PlutusScript,
88 key: String,
89 value: ByteString,
90 ctx: TransactionContext,
91 genesis_utxo: UtxoId,
92 client: &C,
93 await_tx: &A,
94) -> anyhow::Result<MultiSigSmartContractResult> {
95 let governance_data = GovernanceData::get(genesis_utxo, client).await?;
96
97 submit_or_create_tx_to_sign(
98 &governance_data,
99 ctx,
100 |costs, ctx| {
101 insert_key_value_tx(
102 validator,
103 policy,
104 key.clone(),
105 value.clone(),
106 &governance_data,
107 costs,
108 &ctx,
109 )
110 },
111 "Insert Key-Value pair",
112 client,
113 await_tx,
114 )
115 .await
116}
117
118fn insert_key_value_tx(
119 validator: &PlutusScript,
120 policy: &PlutusScript,
121 key: String,
122 value: ByteString,
123 governance_data: &GovernanceData,
124 costs: Costs,
125 ctx: &TransactionContext,
126) -> anyhow::Result<Transaction> {
127 let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
128 tx_builder.add_mint_one_script_token(
129 policy,
130 &empty_asset_name(),
131 &unit_plutus_data(),
132 &costs.get_mint(&policy.clone()),
133 )?;
134 tx_builder.add_output_with_one_script_token(
135 validator,
136 policy,
137 &governed_map_datum_to_plutus_data(&GovernedMapDatum::new(key, value)),
138 ctx,
139 )?;
140
141 let gov_tx_input = governance_data.utxo_id_as_tx_input();
142 tx_builder.add_mint_one_script_token_using_reference_script(
143 &governance_data.policy.script(),
144 &gov_tx_input,
145 &costs,
146 )?;
147
148 Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
149}
150
151pub async fn run_update<
156 C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
157 A: AwaitTx,
158>(
159 genesis_utxo: UtxoId,
160 key: String,
161 value: ByteString,
162 expected_current_value: Option<ByteString>,
163 payment_signing_key: &CardanoPaymentSigningKey,
164 ogmios_client: &C,
165 await_tx: &A,
166) -> anyhow::Result<Option<MultiSigSmartContractResult>> {
167 let ctx = TransactionContext::for_payment_key(payment_signing_key, ogmios_client).await?;
168 let scripts = crate::scripts_data::governed_map_scripts(genesis_utxo, ctx.network)?;
169 let validator_utxos = ogmios_client.query_utxos(&[scripts.validator_address.clone()]).await?;
170 let utxos_for_key =
171 get_utxos_for_key(validator_utxos.clone(), key.clone(), scripts.policy_id());
172
173 let Some(actual_current_value) =
174 get_current_value(validator_utxos.clone(), key.clone(), scripts.policy_id())
175 else {
176 return Err(anyhow!("Cannot update nonexistent key :'{key}'."));
177 };
178
179 if matches!(expected_current_value,
180 Some(ref expected_current_value) if *expected_current_value != actual_current_value)
181 {
182 return Err(anyhow!("Value for key '{key}' is set to a different value than expected."));
183 }
184
185 let tx_hash_opt = {
186 if actual_current_value != value {
187 Some(
188 update(
189 &scripts.validator,
190 &scripts.policy,
191 key,
192 value,
193 &utxos_for_key,
194 ctx,
195 genesis_utxo,
196 ogmios_client,
197 await_tx,
198 )
199 .await?,
200 )
201 } else {
202 log::info!("Value for key '{key}' is already set to the same value. Skipping update.");
203 None
204 }
205 };
206
207 if let Some(TransactionSubmitted(tx_hash)) = tx_hash_opt {
208 await_tx.await_tx_output(ogmios_client, UtxoId::new(tx_hash.0, 0)).await?;
209 }
210 Ok(tx_hash_opt)
211}
212
213async fn update<
214 C: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
215 A: AwaitTx,
216>(
217 validator: &PlutusScript,
218 policy: &PlutusScript,
219 key: String,
220 value: ByteString,
221 utxos_for_key: &[OgmiosUtxo],
222 ctx: TransactionContext,
223 genesis_utxo: UtxoId,
224 client: &C,
225 await_tx: &A,
226) -> anyhow::Result<MultiSigSmartContractResult> {
227 let governance_data = GovernanceData::get(genesis_utxo, client).await?;
228
229 submit_or_create_tx_to_sign(
230 &governance_data,
231 ctx,
232 |costs, ctx| {
233 update_key_value_tx(
234 validator,
235 policy,
236 key.clone(),
237 value.clone(),
238 utxos_for_key,
239 &governance_data,
240 costs,
241 &ctx,
242 )
243 },
244 "Update Key-Value pair",
245 client,
246 await_tx,
247 )
248 .await
249}
250
251fn update_key_value_tx(
252 validator: &PlutusScript,
253 policy: &PlutusScript,
254 key: String,
255 value: ByteString,
256 utxos_for_key: &[OgmiosUtxo],
257 governance_data: &GovernanceData,
258 costs: Costs,
259 ctx: &TransactionContext,
260) -> anyhow::Result<Transaction> {
261 let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
262
263 let gov_tx_input = governance_data.utxo_id_as_tx_input();
264 tx_builder.add_mint_one_script_token_using_reference_script(
265 &governance_data.policy.script(),
266 &gov_tx_input,
267 &costs,
268 )?;
269
270 let spend_indicies = costs.get_spend_indices();
271
272 let mut inputs = TxInputsBuilder::new();
273 for (ix, utxo) in utxos_for_key.iter().enumerate() {
274 inputs.add_script_utxo_input(
275 utxo,
276 validator,
277 &PlutusData::new_bytes(vec![]),
278 &costs.get_spend(*spend_indicies.get(ix).unwrap_or(&0)),
279 )?;
280 }
281 tx_builder.set_inputs(&inputs);
282
283 if utxos_for_key.len() > 1 {
284 let burn_amount = (utxos_for_key.len() as i32 - 1).neg();
285 tx_builder.add_mint_script_tokens(
286 policy,
287 &empty_asset_name(),
288 &unit_plutus_data(),
289 &costs.get_mint(&policy.clone()),
290 &Int::new_i32(burn_amount),
291 )?;
292 }
293
294 tx_builder.add_output_with_one_script_token(
295 validator,
296 policy,
297 &governed_map_datum_to_plutus_data(&GovernedMapDatum::new(key, value)),
298 ctx,
299 )?;
300
301 Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
302}
303
304pub async fn run_remove<
307 C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
308 A: AwaitTx,
309>(
310 genesis_utxo: UtxoId,
311 key: String,
312 payment_signing_key: &CardanoPaymentSigningKey,
313 ogmios_client: &C,
314 await_tx: &A,
315) -> anyhow::Result<Option<MultiSigSmartContractResult>> {
316 let ctx = TransactionContext::for_payment_key(payment_signing_key, ogmios_client).await?;
317 let scripts = crate::scripts_data::governed_map_scripts(genesis_utxo, ctx.network)?;
318 let validator_utxos = ogmios_client.query_utxos(&[scripts.validator_address.clone()]).await?;
319
320 let utxos_for_key = get_utxos_for_key(validator_utxos, key.clone(), scripts.policy_id());
321
322 let tx_hash_opt = match utxos_for_key.len() {
323 0 => {
324 log::info!("There is no value stored for key '{key}'. Skipping remove.");
325 None
326 },
327 _ => Some(
328 remove(
329 &scripts.validator,
330 &scripts.policy,
331 &utxos_for_key,
332 ctx,
333 genesis_utxo,
334 ogmios_client,
335 await_tx,
336 )
337 .await?,
338 ),
339 };
340 if let Some(TransactionSubmitted(tx_hash)) = tx_hash_opt {
341 await_tx.await_tx_output(ogmios_client, UtxoId::new(tx_hash.0, 0)).await?;
342 }
343 Ok(tx_hash_opt)
344}
345
346async fn remove<
347 C: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
348 A: AwaitTx,
349>(
350 validator: &PlutusScript,
351 policy: &PlutusScript,
352 utxos_for_key: &Vec<OgmiosUtxo>,
353 ctx: TransactionContext,
354 genesis_utxo: UtxoId,
355 client: &C,
356 await_tx: &A,
357) -> anyhow::Result<MultiSigSmartContractResult> {
358 let governance_data = GovernanceData::get(genesis_utxo, client).await?;
359
360 submit_or_create_tx_to_sign(
361 &governance_data,
362 ctx,
363 |costs, ctx| {
364 remove_key_value_tx(validator, policy, utxos_for_key, &governance_data, costs, &ctx)
365 },
366 "Remove Key-Value pair",
367 client,
368 await_tx,
369 )
370 .await
371}
372
373fn remove_key_value_tx(
374 validator: &PlutusScript,
375 policy: &PlutusScript,
376 utxos_for_key: &Vec<OgmiosUtxo>,
377 governance_data: &GovernanceData,
378 costs: Costs,
379 ctx: &TransactionContext,
380) -> anyhow::Result<Transaction> {
381 let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
382
383 let gov_tx_input = governance_data.utxo_id_as_tx_input();
384 tx_builder.add_mint_one_script_token_using_reference_script(
385 &governance_data.policy.script(),
386 &gov_tx_input,
387 &costs,
388 )?;
389
390 let spend_indices = costs.get_spend_indices();
391
392 let mut inputs = TxInputsBuilder::new();
393 for (ix, utxo) in utxos_for_key.iter().enumerate() {
394 inputs.add_script_utxo_input(
395 utxo,
396 validator,
397 &unit_plutus_data(),
398 &costs.get_spend(*spend_indices.get(ix).unwrap_or(&0)),
399 )?;
400 }
401 tx_builder.set_inputs(&inputs);
402
403 let burn_amount = (utxos_for_key.len() as i32).neg();
404 tx_builder.add_mint_script_tokens(
405 policy,
406 &empty_asset_name(),
407 &unit_plutus_data(),
408 &costs.get_mint(&policy.clone()),
409 &Int::new_i32(burn_amount),
410 )?;
411
412 Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
413}
414
415pub async fn run_list<C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId>(
417 genesis_utxo: UtxoId,
418 ogmios_client: &C,
419) -> anyhow::Result<impl Iterator<Item = GovernedMapDatum>> {
420 let network = ogmios_client.shelley_genesis_configuration().await?.network.to_csl();
421 let scripts = crate::scripts_data::governed_map_scripts(genesis_utxo, network)?;
422 let validator_utxos = ogmios_client.query_utxos(&[scripts.validator_address.clone()]).await?;
423 Ok(ogmios_utxos_to_governed_map_utxos(validator_utxos.into_iter(), scripts.policy_id())
424 .map(|(_, datum)| datum))
425}
426
427pub async fn run_get<C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId>(
429 genesis_utxo: UtxoId,
430 key: String,
431 ogmios_client: &C,
432) -> anyhow::Result<Option<ByteString>> {
433 let network = ogmios_client.shelley_genesis_configuration().await?.network.to_csl();
434 let scripts = crate::scripts_data::governed_map_scripts(genesis_utxo, network)?;
435 let validator_utxos = ogmios_client.query_utxos(&[scripts.validator_address.clone()]).await?;
436 Ok(get_current_value(validator_utxos, key, scripts.policy_id()))
437}
438
439fn ogmios_utxos_to_governed_map_utxos(
440 utxos: impl Iterator<Item = OgmiosUtxo>,
441 token: PolicyId,
442) -> impl Iterator<Item = (OgmiosUtxo, GovernedMapDatum)> {
443 utxos.flat_map(move |utxo| {
444 let _ = utxo.clone().value.native_tokens.get(&token.0)?;
445 let datum = utxo.clone().datum?;
446 let datum_plutus_data = PlutusData::from_bytes(datum.bytes).ok()?;
447
448 GovernedMapDatum::try_from(datum_plutus_data).ok().and_then(|d| Some((utxo, d)))
449 })
450}
451
452fn get_current_value(
453 validator_utxos: Vec<OgmiosUtxo>,
454 key: String,
455 token: PolicyId,
456) -> Option<ByteString> {
457 ogmios_utxos_to_governed_map_utxos(validator_utxos.into_iter(), token)
458 .find(|(_, datum)| datum.key == key)
459 .map(|(_, datum)| datum.value)
460}
461
462fn get_utxos_for_key(
463 validator_utxos: Vec<OgmiosUtxo>,
464 key: String,
465 token: PolicyId,
466) -> Vec<OgmiosUtxo> {
467 ogmios_utxos_to_governed_map_utxos(validator_utxos.into_iter(), token)
468 .filter(|(_, datum)| datum.key == key)
469 .map(|(utxo, _)| utxo)
470 .collect()
471}
472
473#[allow(dead_code)]
475pub async fn run_insert_with_force<
476 C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
477 A: AwaitTx,
478>(
479 genesis_utxo: UtxoId,
480 key: String,
481 value: ByteString,
482 payment_signing_key: &CardanoPaymentSigningKey,
483 ogmios_client: &C,
484 await_tx: &A,
485) -> anyhow::Result<Option<MultiSigSmartContractResult>> {
486 let ctx = TransactionContext::for_payment_key(payment_signing_key, ogmios_client).await?;
487 let scripts = crate::scripts_data::governed_map_scripts(genesis_utxo, ctx.network)?;
488
489 let tx_hash_opt = Some(
490 insert(
491 &scripts.validator,
492 &scripts.policy,
493 key,
494 value,
495 ctx,
496 genesis_utxo,
497 ogmios_client,
498 await_tx,
499 )
500 .await?,
501 );
502
503 if let Some(TransactionSubmitted(tx_hash)) = tx_hash_opt {
504 await_tx.await_tx_output(ogmios_client, UtxoId::new(tx_hash.0, 0)).await?;
505 }
506 Ok(tx_hash_opt)
507}