1use crate::{
18 await_tx::AwaitTx,
19 cardano_keys::CardanoPaymentSigningKey,
20 csl::{
21 CostStore, Costs, MultiAssetExt, OgmiosUtxoExt, TransactionBuilderExt, TransactionContext,
22 TransactionExt, TransactionOutputAmountBuilderExt, get_builder_config,
23 },
24 governance::GovernanceData,
25 multisig::{MultiSigSmartContractResult, submit_or_create_tx_to_sign},
26 plutus_script::PlutusScript,
27 scripts_data::{self, PlutusScriptData},
28};
29use cardano_serialization_lib::{
30 AssetName, BigNum, ConstrPlutusData, JsError, MultiAsset, PlutusData, PlutusList, ScriptRef,
31 Transaction, TransactionBuilder, TransactionOutputBuilder,
32};
33use ogmios_client::{
34 query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
35 query_network::QueryNetwork,
36 transactions::Transactions,
37 types::OgmiosUtxo,
38};
39use partner_chains_plutus_data::version_oracle::VersionOracleDatum;
40use raw_scripts::{
41 ILLIQUID_CIRCULATION_SUPPLY_VALIDATOR, RESERVE_AUTH_POLICY, RESERVE_VALIDATOR, ScriptId,
42};
43use sidechain_domain::UtxoId;
44
45pub async fn init_reserve_management<
51 T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
52 A: AwaitTx,
53>(
54 genesis_utxo: UtxoId,
55 payment_key: &CardanoPaymentSigningKey,
56 client: &T,
57 await_tx: &A,
58) -> anyhow::Result<Vec<MultiSigSmartContractResult>> {
59 let reserve_validator = ScriptData::new(
60 "Reserve Management Validator",
61 RESERVE_VALIDATOR.0.to_vec(),
62 ScriptId::ReserveValidator,
63 );
64 let reserve_policy = ScriptData::new(
65 "Reserve Management Policy",
66 RESERVE_AUTH_POLICY.0.to_vec(),
67 ScriptId::ReserveAuthPolicy,
68 );
69 let ics_validator = ScriptData::new(
70 "Illiquid Circulation Supply Validator",
71 ILLIQUID_CIRCULATION_SUPPLY_VALIDATOR.0.to_vec(),
72 ScriptId::IlliquidCirculationSupplyValidator,
73 );
74 Ok(vec![
75 initialize_script(reserve_validator, genesis_utxo, payment_key, client, await_tx).await?,
76 initialize_script(reserve_policy, genesis_utxo, payment_key, client, await_tx).await?,
77 initialize_script(ics_validator, genesis_utxo, payment_key, client, await_tx).await?,
78 ]
79 .into_iter()
80 .flatten()
81 .collect())
82}
83
84struct ScriptData {
85 name: String,
86 plutus_script: PlutusScript,
87 id: u32,
88}
89
90impl ScriptData {
91 fn new(name: &str, raw_bytes: Vec<u8>, id: ScriptId) -> Self {
92 let plutus_script =
93 PlutusScript::from_wrapped_cbor(&raw_bytes).expect("Plutus script should be valid");
94 Self { name: name.to_string(), plutus_script, id: id as u32 }
95 }
96
97 fn applied_plutus_script(
98 &self,
99 version_oracle: &PlutusScriptData,
100 ) -> Result<PlutusScript, JsError> {
101 self.plutus_script
102 .clone()
103 .apply_data_uplc(version_oracle.policy_id_as_plutus_data())
104 .map_err(|e| JsError::from_str(&e.to_string()))
105 }
106}
107
108async fn initialize_script<
109 T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
110 A: AwaitTx,
111>(
112 script: ScriptData,
113 genesis_utxo: UtxoId,
114 payment_key: &CardanoPaymentSigningKey,
115 client: &T,
116 await_tx: &A,
117) -> anyhow::Result<Option<MultiSigSmartContractResult>> {
118 let payment_ctx = TransactionContext::for_payment_key(payment_key, client).await?;
119 let governance = GovernanceData::get(genesis_utxo, client).await?;
120 let version_oracle = scripts_data::version_oracle(genesis_utxo, payment_ctx.network)?;
121
122 if script_is_initialized(&script, &version_oracle, &payment_ctx, client).await? {
123 log::info!("Script '{}' is already initialized", script.name);
124 return Ok(None);
125 }
126 Ok(Some(
127 submit_or_create_tx_to_sign(
128 &governance,
129 payment_ctx,
130 |costs, ctx| init_script_tx(&script, &governance, &version_oracle, costs, &ctx),
131 "Init Reserve Management",
132 client,
133 await_tx,
134 )
135 .await?,
136 ))
137}
138
139fn init_script_tx(
140 script: &ScriptData,
141 governance: &GovernanceData,
142 version_oracle: &PlutusScriptData,
143 costs: Costs,
144 ctx: &TransactionContext,
145) -> anyhow::Result<Transaction> {
146 let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
147 let applied_script = script.applied_plutus_script(version_oracle)?;
148 {
149 let witness = PlutusData::new_constr_plutus_data(&ConstrPlutusData::new(
150 &BigNum::one(),
151 &version_oracle_plutus_list(script.id, &applied_script.script_hash()),
152 ));
153 tx_builder.add_mint_one_script_token(
154 &version_oracle.policy,
155 &version_oracle_asset_name(),
156 &witness,
157 &costs.get_mint(&version_oracle.policy.clone()),
158 )?;
159 }
160 {
161 let script_ref = ScriptRef::new_plutus_script(&applied_script.to_csl());
162 let amount_builder = TransactionOutputBuilder::new()
163 .with_address(&version_oracle.validator.address(ctx.network))
164 .with_plutus_data(&PlutusData::new_list(&version_oracle_plutus_list(
165 script.id,
166 &version_oracle.policy.script_hash(),
167 )))
168 .with_script_ref(&script_ref)
169 .next()?;
170 let ma = MultiAsset::new()
171 .with_asset_amount(&version_oracle.policy.asset(version_oracle_asset_name())?, 1u64)?;
172 let output = amount_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()?;
173 tx_builder.add_output(&output)?;
174 }
175 let gov_tx_input = governance.utxo_id_as_tx_input();
177 tx_builder.add_mint_one_script_token_using_reference_script(
178 &governance.policy.script(),
179 &gov_tx_input,
180 &costs,
181 )?;
182 Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
183}
184
185fn version_oracle_asset_name() -> AssetName {
186 AssetName::new(b"Version oracle".to_vec()).unwrap()
187}
188
189fn version_oracle_plutus_list(script_id: u32, script_hash: &[u8]) -> PlutusList {
190 let mut list = PlutusList::new();
191 list.add(&PlutusData::new_integer(&script_id.into()));
192 list.add(&PlutusData::new_bytes(script_hash.to_vec()));
193 list
194}
195
196async fn script_is_initialized<T: QueryLedgerState>(
200 script: &ScriptData,
201 version_oracle: &PlutusScriptData,
202 ctx: &TransactionContext,
203 client: &T,
204) -> Result<bool, anyhow::Error> {
205 Ok(find_script_utxo(script.id, version_oracle, ctx, client).await?.is_some())
206}
207
208pub(crate) async fn find_script_utxo<T: QueryLedgerState>(
212 script_id: u32,
213 version_oracle: &PlutusScriptData,
214 ctx: &TransactionContext,
215 client: &T,
216) -> Result<Option<OgmiosUtxo>, anyhow::Error> {
217 let validator_address = version_oracle.validator.address(ctx.network).to_bech32(None)?;
218 let validator_utxos = client.query_utxos(&[validator_address]).await?;
219 Ok(validator_utxos.into_iter().find(|utxo| {
221 utxo.get_plutus_data()
222 .and_then(|data| TryInto::<VersionOracleDatum>::try_into(data).ok())
223 .is_some_and(|datum| {
224 datum.version_oracle == script_id
225 && datum.currency_symbol == version_oracle.policy.script_hash()
226 })
227 }))
228}
229
230#[cfg(test)]
231mod tests {
232 use super::{ScriptData, init_script_tx};
233 use crate::{
234 csl::{Costs, OgmiosUtxoExt, TransactionContext, unit_plutus_data},
235 governance::GovernanceData,
236 scripts_data::{self, PlutusScriptData},
237 test_values::{
238 make_utxo, payment_addr, payment_key, protocol_parameters, test_governance_policy,
239 },
240 };
241 use cardano_serialization_lib::{
242 AssetName, BigNum, ConstrPlutusData, ExUnits, Int, NetworkIdKind, PlutusData, PlutusList,
243 RedeemerTag, ScriptHash, Transaction,
244 };
245 use ogmios_client::types::{OgmiosTx, OgmiosUtxo};
246 use raw_scripts::ScriptId;
247 use sidechain_domain::UtxoId;
248
249 #[test]
250 fn init_script_tx_version_oracle_output_test() {
251 let outputs = make_init_script_tx().body().outputs();
252 let voo = outputs
253 .into_iter()
254 .find(|o| o.address() == version_oracle_validator_address())
255 .expect("Init Script Transaction should have output to Version Oracle Validator");
256 let voo_script_ref = voo
257 .script_ref()
258 .expect("Version Oracle Validator output should have script reference");
259 let voo_plutus_script = voo_script_ref
260 .plutus_script()
261 .expect("Script reference should be Plutus script");
262 assert_eq!(
263 voo_plutus_script,
264 test_initialized_script()
265 .applied_plutus_script(&version_oracle_data())
266 .unwrap()
267 .to_csl()
268 );
269 let amount = voo
270 .amount()
271 .multiasset()
272 .and_then(|ma| ma.get(&version_oracle_policy_csl_script_hash()))
273 .and_then(|vo_ma| vo_ma.get(&AssetName::new(b"Version oracle".to_vec()).unwrap()))
274 .expect("Version Oracle Validator output has a token of Version Oracle Policy");
275
276 assert_eq!(amount, 1u64.into());
277
278 let voo_plutus_data = voo
279 .plutus_data()
280 .and_then(|pd| pd.as_list())
281 .expect("Version Oracle Validator output should have Plutus Data of List type");
282 assert_eq!(
283 voo_plutus_data.get(0),
284 PlutusData::new_integer(&test_initialized_script().id.into())
285 );
286 assert_eq!(
287 voo_plutus_data.get(1),
288 PlutusData::new_bytes(version_oracle_data().policy.script_hash().to_vec())
289 );
290 }
291
292 #[test]
293 fn init_script_tx_change_output_test() {
294 let outputs = make_init_script_tx().body().outputs();
295 let change_output = outputs
296 .into_iter()
297 .find(|o| o.address() == payment_addr())
298 .expect("Change output has to be present to keep governance token")
299 .clone();
300 let gov_token_amount = change_output
301 .amount()
302 .multiasset()
303 .and_then(|ma| ma.get(&test_governance_data().policy.script().script_hash().into()))
304 .and_then(|gov_ma| gov_ma.get(&AssetName::new(vec![]).unwrap()))
305 .unwrap();
306 assert_eq!(gov_token_amount, BigNum::one(), "Change contains one governance token");
307 }
308
309 #[test]
310 fn init_script_tx_reference_input() {
311 let ref_input = make_init_script_tx()
313 .body()
314 .reference_inputs()
315 .expect("Init transaction should have reference input")
316 .get(0);
317 assert_eq!(ref_input, test_governance_input().to_csl_tx_input());
318 }
319
320 #[test]
321 fn init_script_tx_mints() {
322 let tx = make_init_script_tx();
323 let vo_mint = tx
324 .body()
325 .mint()
326 .and_then(|mint| mint.get(&version_oracle_policy_csl_script_hash()))
327 .and_then(|assets| assets.get(0))
328 .and_then(|assets| assets.get(&AssetName::new(b"Version oracle".to_vec()).unwrap()))
329 .expect("Transaction should have a mint of Version Oracle Policy token");
330 assert_eq!(vo_mint, Int::new_i32(1));
331
332 let gov_mint = tx
333 .body()
334 .mint()
335 .and_then(|mint| mint.get(&test_governance_data().policy.script().script_hash().into()))
336 .and_then(|assets| assets.get(0))
337 .and_then(|assets| assets.get(&AssetName::new(vec![]).unwrap()))
338 .expect("Transaction should have a mint of Governance Policy token");
339 assert_eq!(gov_mint, Int::new_i32(1));
340 }
341
342 #[test]
343 fn init_script_tx_redeemers() {
344 let ws = make_init_script_tx()
346 .witness_set()
347 .redeemers()
348 .expect("Transaction has two redeemers");
349 let redeemers = vec![ws.get(0), ws.get(1)];
350
351 let expected_vo_redeemer_data = {
352 let mut list = PlutusList::new();
353 let script_id: u64 = test_initialized_script().id.into();
354 list.add(&PlutusData::new_integer(&script_id.into()));
355 list.add(&PlutusData::new_bytes(
356 test_initialized_script()
357 .applied_plutus_script(&version_oracle_data())
358 .unwrap()
359 .script_hash()
360 .to_vec(),
361 ));
362 let constr_plutus_data = ConstrPlutusData::new(&BigNum::one(), &list);
363 PlutusData::new_constr_plutus_data(&constr_plutus_data)
364 };
365
366 let _ = redeemers
367 .iter()
368 .find(|r| {
369 r.tag() == RedeemerTag::new_mint()
370 && r.data() == expected_vo_redeemer_data
371 && r.ex_units() == versioning_script_cost()
372 })
373 .expect("Transaction should have a mint redeemer for Version Oracle Policy");
374 let _ = redeemers
375 .iter()
376 .find(|r| {
377 r.tag() == RedeemerTag::new_mint()
378 && r.data() == unit_plutus_data()
379 && r.ex_units() == governance_script_cost()
380 })
381 .expect("Transaction should have a mint redeemer for Governance Policy");
382 }
383
384 fn make_init_script_tx() -> Transaction {
385 init_script_tx(
386 &test_initialized_script(),
387 &test_governance_data(),
388 &version_oracle_data(),
389 test_costs(),
390 &test_transaction_context(),
391 )
392 .unwrap()
393 }
394
395 fn test_initialized_script() -> ScriptData {
396 ScriptData::new(
397 "Test Script",
398 raw_scripts::RESERVE_VALIDATOR.0.to_vec(),
399 ScriptId::ReserveValidator,
400 )
401 }
402
403 fn test_governance_input() -> OgmiosUtxo {
404 OgmiosUtxo { transaction: OgmiosTx { id: [16; 32] }, index: 0, ..Default::default() }
405 }
406
407 fn test_governance_data() -> GovernanceData {
408 GovernanceData { policy: test_governance_policy(), utxo: test_governance_input() }
409 }
410
411 fn version_oracle_data() -> PlutusScriptData {
412 scripts_data::version_oracle(UtxoId::new([255u8; 32], 0), NetworkIdKind::Testnet).unwrap()
413 }
414
415 fn version_oracle_validator_address() -> cardano_serialization_lib::Address {
416 version_oracle_data().validator.address(NetworkIdKind::Testnet)
417 }
418
419 fn version_oracle_policy_csl_script_hash() -> ScriptHash {
420 version_oracle_data().policy.csl_script_hash()
421 }
422
423 fn test_transaction_context() -> TransactionContext {
424 TransactionContext {
425 payment_key: payment_key(),
426 payment_key_utxos: vec![make_utxo(121u8, 3, 996272387, &payment_addr())],
427 network: NetworkIdKind::Testnet,
428 protocol_parameters: protocol_parameters(),
429 change_address: payment_addr(),
430 }
431 }
432
433 fn governance_script_cost() -> ExUnits {
434 ExUnits::new(&100u64.into(), &200u64.into())
435 }
436
437 fn versioning_script_cost() -> ExUnits {
438 ExUnits::new(&300u64.into(), &400u64.into())
439 }
440
441 fn test_costs() -> Costs {
442 Costs::new(
443 vec![
444 (test_governance_policy().script().script_hash().into(), governance_script_cost()),
445 (version_oracle_policy_csl_script_hash(), versioning_script_cost()),
446 ]
447 .into_iter()
448 .collect(),
449 std::collections::HashMap::new(),
450 )
451 }
452}