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