partner_chains_cardano_offchain/bridge/
mod.rs

1//! Offchain actions to initialize bridge (IlliquidCirculionSupply) and make deposits.
2
3mod create_utxos;
4mod deposit;
5mod init;
6
7use crate::{
8	csl::{OgmiosUtxoExt, OgmiosValueExt, TransactionContext},
9	scripts_data,
10	versioning_system::find_script_utxo,
11};
12use anyhow::anyhow;
13use cardano_serialization_lib::{
14	BigInt, ExUnits, JsError, PlutusData, PlutusScriptSource, PlutusWitness, Redeemer, RedeemerTag,
15	TxInputsBuilder,
16};
17pub use create_utxos::create_validator_utxos;
18pub use deposit::{deposit_with_ics_spend, deposit_without_ics_input};
19pub use init::init_ics_scripts;
20use ogmios_client::{query_ledger_state::QueryLedgerState, types::OgmiosUtxo};
21use sidechain_domain::{AssetId, AssetName, UtxoId, crypto::blake2b};
22
23/// Illiquid Circulation Supply smart contracts data
24#[derive(Clone, Debug)]
25pub(crate) struct ICSData {
26	pub(crate) scripts: scripts_data::ICSScripts,
27	/// Versioning System UTXO that keeps a policy of tokens used to maintain some minimal number of UTXOs at the validator.
28	pub(crate) auth_policy_version_utxo: OgmiosUtxo,
29	pub(crate) validator_version_utxo: OgmiosUtxo,
30}
31
32impl ICSData {
33	pub(crate) async fn get<T: QueryLedgerState>(
34		genesis_utxo: UtxoId,
35		ctx: &TransactionContext,
36		client: &T,
37	) -> Result<Self, anyhow::Error> {
38		let version_oracle = scripts_data::version_oracle(genesis_utxo, ctx.network)?;
39		let validator_version_utxo = find_script_utxo(
40			raw_scripts::ScriptId::IlliquidCirculationSupplyValidator,
41			&version_oracle,
42			ctx,
43			client,
44		)
45		.await?
46		.ok_or_else(|| {
47			anyhow!(
48				"Illiquid Circulation Supply Validator Version Utxo not found, is the Bridge initialized?"
49			)
50		})?;
51		let auth_policy_version_utxo = find_script_utxo(
52			raw_scripts::ScriptId::IlliquidCirculationSupplyAuthorityTokenPolicy,
53			&version_oracle,
54			ctx,
55			client,
56		)
57		.await?
58		.ok_or_else(|| {
59			anyhow!("Illiquid Circulation Supply Authority Token Policy Version Utxo not found, is the Bridge initialized?")
60		})?;
61		let scripts = scripts_data::ics_scripts(genesis_utxo, ctx.network)?;
62		Ok(ICSData { scripts, auth_policy_version_utxo, validator_version_utxo })
63	}
64
65	pub(crate) async fn get_validator_utxos_with_auth_token<T: QueryLedgerState>(
66		&self,
67		ctx: &TransactionContext,
68		client: &T,
69	) -> Result<Vec<OgmiosUtxo>, anyhow::Error> {
70		let validator_address = self.scripts.validator.address(ctx.network).to_bech32(None)?;
71		let validator_utxos = client.query_utxos(&[validator_address]).await?;
72
73		let auth_token_asset_id = AssetId {
74			policy_id: self.scripts.auth_policy.policy_id(),
75			asset_name: AssetName::empty(),
76		};
77
78		Ok(validator_utxos
79			.into_iter()
80			.filter(|utxo| utxo.get_asset_amount(&auth_token_asset_id) == 1u64)
81			.collect())
82	}
83}
84
85/// Selects one from input utxos. To avoid randomness we take the one that combined with user own utxo has the lowest hash.
86pub(crate) fn select_utxo_to_spend(
87	utxos: &[OgmiosUtxo],
88	ctx: &TransactionContext,
89) -> Option<OgmiosUtxo> {
90	utxos
91		.into_iter()
92		.map(|u| {
93			let utxo_id = u.utxo_id();
94			let mut v: Vec<u8> = utxo_id.tx_hash.0.to_vec();
95			v.append(&mut utxo_id.index.0.to_be_bytes().to_vec());
96			v.append(&mut ctx.payment_key_hash().to_bytes());
97			let hash: [u8; 32] = blake2b(&v);
98			(hash, u)
99		})
100		.min_by_key(|k| k.0)
101		.map(|kv| kv.1.clone())
102}
103
104pub(crate) fn add_ics_utxo_input_with_validator_script_reference(
105	inputs: &mut TxInputsBuilder,
106	ics_utxo: &OgmiosUtxo,
107	ics_data: &ICSData,
108	cost: &ExUnits,
109) -> Result<(), JsError> {
110	let input = ics_utxo.to_csl_tx_input();
111	let amount = ics_utxo.value.to_csl()?;
112	let script = &ics_data.scripts.validator;
113	let witness = PlutusWitness::new_with_ref_without_datum(
114		&PlutusScriptSource::new_ref_input(
115			&script.csl_script_hash(),
116			&ics_data.validator_version_utxo.to_csl_tx_input(),
117			&script.language,
118			script.bytes.len(),
119		),
120		&Redeemer::new(
121			&RedeemerTag::new_spend(),
122			&0u32.into(),
123			&PlutusData::new_integer(&BigInt::zero()),
124			cost,
125		),
126	);
127	inputs.add_plutus_script_input(&witness, &input, &amount);
128	Ok(())
129}