partner_chains_cardano_offchain/
csl.rs

1use crate::cardano_keys::CardanoPaymentSigningKey;
2use crate::plutus_script::PlutusScript;
3use cardano_serialization_lib::*;
4use fraction::{FromPrimitive, Ratio};
5use ogmios_client::query_ledger_state::ReferenceScriptsCosts;
6use ogmios_client::transactions::Transactions;
7use ogmios_client::{
8	query_ledger_state::{PlutusCostModels, ProtocolParametersResponse, QueryLedgerState},
9	query_network::QueryNetwork,
10	transactions::OgmiosEvaluateTransactionResponse,
11	types::{OgmiosUtxo, OgmiosValue},
12};
13use sidechain_domain::{AssetId, NetworkType, UtxoId};
14use std::collections::HashMap;
15
16/// Constructs [Transaction] from CBOR bytes
17pub fn transaction_from_bytes(cbor: Vec<u8>) -> anyhow::Result<Transaction> {
18	Transaction::from_bytes(cbor).map_err(|e| anyhow::anyhow!(e))
19}
20
21/// Constructs [Vkeywitness] from CBOR bytes
22pub fn vkey_witness_from_bytes(cbor: Vec<u8>) -> anyhow::Result<Vkeywitness> {
23	Vkeywitness::from_bytes(cbor).map_err(|e| anyhow::anyhow!(e))
24}
25
26pub(crate) fn plutus_script_hash(script_bytes: &[u8], language: Language) -> [u8; 28] {
27	// Before hashing the script, we need to prepend with byte denoting the language.
28	let mut buf: Vec<u8> = vec![language_to_u8(language)];
29	buf.extend(script_bytes);
30	sidechain_domain::crypto::blake2b(buf.as_slice())
31}
32
33/// Builds a CSL [Address] for plutus script from the data obtained from smart contracts.
34pub fn script_address(script_bytes: &[u8], network: NetworkIdKind, language: Language) -> Address {
35	let script_hash = plutus_script_hash(script_bytes, language);
36	EnterpriseAddress::new(
37		network_id_kind_to_u8(network),
38		&Credential::from_scripthash(&script_hash.into()),
39	)
40	.to_address()
41}
42
43/// Builds a CSL [Address] for the specified network from Cardano verification key bytes.
44pub fn payment_address(cardano_verification_key_bytes: &[u8], network: NetworkIdKind) -> Address {
45	let key_hash = sidechain_domain::crypto::blake2b(cardano_verification_key_bytes);
46	EnterpriseAddress::new(
47		network_id_kind_to_u8(network),
48		&Credential::from_keyhash(&key_hash.into()),
49	)
50	.to_address()
51}
52
53/// Builds a CSL [Address] for the specified network from a Cardano verification key hash.
54pub fn key_hash_address(pub_key_hash: &Ed25519KeyHash, network: NetworkIdKind) -> Address {
55	EnterpriseAddress::new(network_id_kind_to_u8(network), &Credential::from_keyhash(pub_key_hash))
56		.to_address()
57}
58
59/// Extension trait for [NetworkType].
60pub trait NetworkTypeExt {
61	/// Converts [NetworkType] to CSL [cardano_serialization_lib::Value].
62	fn to_csl(&self) -> NetworkIdKind;
63}
64impl NetworkTypeExt for NetworkType {
65	fn to_csl(&self) -> NetworkIdKind {
66		match self {
67			Self::Mainnet => NetworkIdKind::Mainnet,
68			Self::Testnet => NetworkIdKind::Testnet,
69		}
70	}
71}
72
73fn network_id_kind_to_u8(network: NetworkIdKind) -> u8 {
74	match network {
75		NetworkIdKind::Mainnet => 1,
76		NetworkIdKind::Testnet => 0,
77	}
78}
79
80fn language_to_u8(language: Language) -> u8 {
81	match language.kind() {
82		LanguageKind::PlutusV1 => 1,
83		LanguageKind::PlutusV2 => 2,
84		LanguageKind::PlutusV3 => 3,
85	}
86}
87
88/// Creates a CSL [`TransactionBuilderConfig`] for given [`ProtocolParametersResponse`].
89/// This function is not unit-testable because [`TransactionBuilderConfig`] has no public getters.
90pub(crate) fn get_builder_config(
91	context: &TransactionContext,
92) -> Result<TransactionBuilderConfig, JsError> {
93	let protocol_parameters = &context.protocol_parameters;
94	TransactionBuilderConfigBuilder::new()
95		.fee_algo(&linear_fee(protocol_parameters))
96		.pool_deposit(&protocol_parameters.stake_pool_deposit.to_csl()?.coin())
97		.key_deposit(&protocol_parameters.stake_credential_deposit.to_csl()?.coin())
98		.max_value_size(protocol_parameters.max_value_size.bytes)
99		.max_tx_size(protocol_parameters.max_transaction_size.bytes)
100		.ex_unit_prices(&ExUnitPrices::new(
101			&ratio_to_unit_interval(&protocol_parameters.script_execution_prices.memory),
102			&ratio_to_unit_interval(&protocol_parameters.script_execution_prices.cpu),
103		))
104		.coins_per_utxo_byte(&protocol_parameters.min_utxo_deposit_coefficient.into())
105		.ref_script_coins_per_byte(&convert_reference_script_costs(
106			&protocol_parameters.min_fee_reference_scripts.clone(),
107		)?)
108		.deduplicate_explicit_ref_inputs_with_regular_inputs(true)
109		.build()
110}
111
112fn linear_fee(protocol_parameters: &ProtocolParametersResponse) -> LinearFee {
113	let constant: BigNum = protocol_parameters.min_fee_constant.lovelace.into();
114	LinearFee::new(&protocol_parameters.min_fee_coefficient.into(), &constant)
115}
116
117fn ratio_to_unit_interval(ratio: &fraction::Ratio<u64>) -> UnitInterval {
118	UnitInterval::new(&(*ratio.numer()).into(), &(*ratio.denom()).into())
119}
120
121/// Extension trait for [OgmiosValue].
122pub trait OgmiosValueExt {
123	/// Converts [OgmiosValue] to CSL [cardano_serialization_lib::Value].
124	/// It can fail if the input contains negative values, for example Ogmios values representing burn.
125	fn to_csl(&self) -> Result<Value, JsError>;
126}
127
128impl OgmiosValueExt for OgmiosValue {
129	fn to_csl(&self) -> Result<Value, JsError> {
130		if !self.native_tokens.is_empty() {
131			let mut multiasset = MultiAsset::new();
132			for (policy_id, assets) in self.native_tokens.iter() {
133				let mut csl_assets = Assets::new();
134				for asset in assets.iter() {
135					let asset_name = AssetName::new(asset.name.clone()).map_err(|e| {
136						JsError::from_str(&format!(
137							"Could not convert Ogmios UTXO value, asset name is invalid: '{}'",
138							e
139						))
140					})?;
141					csl_assets.insert(&asset_name, &asset.amount.into());
142				}
143				multiasset.insert(&ScriptHash::from(*policy_id), &csl_assets);
144			}
145			Ok(Value::new_with_assets(&self.lovelace.into(), &multiasset))
146		} else {
147			Ok(Value::new(&self.lovelace.into()))
148		}
149	}
150}
151
152/// Conversion of ogmios-client cost models to CSL
153pub(crate) fn convert_cost_models(m: &PlutusCostModels) -> Costmdls {
154	let mut mdls = Costmdls::new();
155	mdls.insert(&Language::new_plutus_v1(), &CostModel::from(m.plutus_v1.to_owned()));
156	mdls.insert(&Language::new_plutus_v2(), &CostModel::from(m.plutus_v2.to_owned()));
157	mdls.insert(&Language::new_plutus_v3(), &CostModel::from(m.plutus_v3.to_owned()));
158	mdls
159}
160
161pub(crate) fn convert_reference_script_costs(
162	costs: &ReferenceScriptsCosts,
163) -> Result<UnitInterval, JsError> {
164	let r = Ratio::<u64>::from_f64(costs.base).ok_or_else(|| {
165		JsError::from_str(&format!("Failed to decode cost base {} as a u64 ratio", costs.base))
166	})?;
167	let numerator = BigNum::from(*r.numer());
168	let denominator = BigNum::from(*r.denom());
169	Ok(UnitInterval::new(&numerator, &denominator))
170}
171
172fn ex_units_from_response(resp: OgmiosEvaluateTransactionResponse) -> ExUnits {
173	ExUnits::new(&resp.budget.memory.into(), &resp.budget.cpu.into())
174}
175
176/// Type representing transaction execution costs.
177pub(crate) enum Costs {
178	/// Zero costs. Used as a dummy value when submitting a transaction for cost calculation.
179	ZeroCosts,
180	/// Variant containing actual costs.
181	Costs {
182		/// Mapping script hashes to minting policy execution costs.
183		mints: HashMap<cardano_serialization_lib::ScriptHash, ExUnits>,
184		/// Mapping spend indices to validator script execution costs.
185		spends: HashMap<u32, ExUnits>,
186	},
187}
188
189/// Interface for retrieving execution costs.
190pub(crate) trait CostStore {
191	/// Returns [ExUnits] cost of a minting policy for a given [PlutusScript].
192	fn get_mint(&self, script: &PlutusScript) -> ExUnits;
193	/// Returns [ExUnits] cost of a validator script for a given spend index.
194	fn get_spend(&self, spend_ix: u32) -> ExUnits;
195	/// Returns spend cost of the single validator script in a transaction.
196	/// It panics if there is not exactly one validator script execution.
197	fn get_one_spend(&self) -> ExUnits;
198	/// Returns indices of validator scripts as they appear in the CSL transaction.
199	/// These indices can be used in conjunction with [get_spend].
200	fn get_spend_indices(&self) -> Vec<u32>;
201}
202
203impl CostStore for Costs {
204	fn get_mint(&self, script: &PlutusScript) -> ExUnits {
205		match self {
206			Costs::ZeroCosts => zero_ex_units(),
207			Costs::Costs { mints, .. } => mints
208				.get(&script.csl_script_hash())
209				.expect("get_mint should not be called with an unknown script")
210				.clone(),
211		}
212	}
213	fn get_spend(&self, spend_ix: u32) -> ExUnits {
214		match self {
215			Costs::ZeroCosts => zero_ex_units(),
216			Costs::Costs { spends, .. } => spends
217				.get(&spend_ix)
218				.expect("get_spend should not be called with an unknown spend index")
219				.clone(),
220		}
221	}
222	fn get_one_spend(&self) -> ExUnits {
223		match self {
224			Costs::ZeroCosts => zero_ex_units(),
225			Costs::Costs { spends, .. } => match spends.values().collect::<Vec<_>>()[..] {
226				[x] => x.clone(),
227				_ => panic!(
228					"get_one_spend should only be called when exactly one spend is expected to be present"
229				),
230			},
231		}
232	}
233	fn get_spend_indices(&self) -> Vec<u32> {
234		match self {
235			Costs::ZeroCosts => vec![],
236			Costs::Costs { spends, .. } => spends.keys().cloned().collect(),
237		}
238	}
239}
240
241impl Costs {
242	#[cfg(test)]
243	/// Constructs new [Costs] with given `mints` and `spends`.
244	pub(crate) fn new(
245		mints: HashMap<cardano_serialization_lib::ScriptHash, ExUnits>,
246		spends: HashMap<u32, ExUnits>,
247	) -> Costs {
248		Costs::Costs { mints, spends }
249	}
250
251	/// Creates a [Transaction] with correctly set script execution costs.
252	///
253	/// Arguments:
254	///  - `make_tx`: A function that takes a [Costs] value, and returns a [anyhow::Result<Transaction>].
255	///               This function is meant to describe which execution cost is used where in the transaction.
256	///  - `client`: Ogmios client
257	pub(crate) async fn calculate_costs<T: Transactions, F>(
258		make_tx: F,
259		client: &T,
260	) -> anyhow::Result<Transaction>
261	where
262		F: Fn(Costs) -> anyhow::Result<Transaction>,
263	{
264		// This double evaluation is needed to correctly set costs in some cases.
265		let tx = make_tx(Costs::ZeroCosts)?;
266		// stage 1
267		let costs = Self::from_ogmios(&tx, client).await?;
268
269		let tx = make_tx(costs)?;
270		// stage 2
271		let costs = Self::from_ogmios(&tx, client).await?;
272
273		make_tx(costs)
274	}
275
276	async fn from_ogmios<T: Transactions>(tx: &Transaction, client: &T) -> anyhow::Result<Costs> {
277		let evaluate_response = client.evaluate_transaction(&tx.to_bytes()).await?;
278
279		let mut mints = HashMap::new();
280		let mut spends = HashMap::new();
281		for er in evaluate_response {
282			match er.validator.purpose.as_str() {
283				"mint" => {
284					mints.insert(
285						tx.body()
286							.mint()
287							.expect(
288								"tx.body.mint() should not be empty if we received a 'mint' response from Ogmios",
289							)
290							.keys()
291							.get(er.validator.index as usize),
292						ex_units_from_response(er),
293					);
294				},
295				"spend" => {
296					spends.insert(er.validator.index, ex_units_from_response(er));
297				},
298				_ => {},
299			}
300		}
301
302		Ok(Costs::Costs { mints, spends })
303	}
304}
305
306pub(crate) fn empty_asset_name() -> AssetName {
307	AssetName::new(vec![]).expect("Hardcoded empty asset name is valid")
308}
309
310fn zero_ex_units() -> ExUnits {
311	ExUnits::new(&BigNum::zero(), &BigNum::zero())
312}
313
314pub(crate) trait OgmiosUtxoExt {
315	fn to_csl_tx_input(&self) -> TransactionInput;
316	fn to_csl_tx_output(&self) -> Result<TransactionOutput, JsError>;
317	fn to_csl(&self) -> Result<TransactionUnspentOutput, JsError>;
318
319	fn get_asset_amount(&self, asset: &AssetId) -> u64;
320
321	fn get_plutus_data(&self) -> Option<PlutusData>;
322}
323
324impl OgmiosUtxoExt for OgmiosUtxo {
325	fn to_csl_tx_input(&self) -> TransactionInput {
326		TransactionInput::new(&TransactionHash::from(self.transaction.id), self.index.into())
327	}
328
329	fn to_csl_tx_output(&self) -> Result<TransactionOutput, JsError> {
330		let mut tx_out = TransactionOutput::new(
331			&Address::from_bech32(&self.address).map_err(|e| {
332				JsError::from_str(&format!("Couldn't convert address from ogmios: '{}'", e))
333			})?,
334			&self.value.to_csl()?,
335		);
336		if let Some(script) = self.script.clone() {
337			let plutus_script_ref_opt =
338				script.clone().try_into().ok().map(|plutus_script: PlutusScript| {
339					ScriptRef::new_plutus_script(&plutus_script.to_csl())
340				});
341			let script_ref_opt = plutus_script_ref_opt.or_else(|| {
342				NativeScript::from_bytes(script.cbor)
343					.ok()
344					.map(|native_script| ScriptRef::new_native_script(&native_script))
345			});
346			if let Some(script_ref) = script_ref_opt {
347				tx_out.set_script_ref(&script_ref);
348			}
349		}
350		if let Some(data) = self.get_plutus_data() {
351			tx_out.set_plutus_data(&data);
352		}
353		Ok(tx_out)
354	}
355
356	fn to_csl(&self) -> Result<TransactionUnspentOutput, JsError> {
357		Ok(TransactionUnspentOutput::new(&self.to_csl_tx_input(), &self.to_csl_tx_output()?))
358	}
359
360	fn get_asset_amount(&self, asset_id: &AssetId) -> u64 {
361		self.value
362			.native_tokens
363			.get(&asset_id.policy_id.0)
364			.cloned()
365			.unwrap_or_default()
366			.iter()
367			.find(|asset| asset.name == asset_id.asset_name.0.to_vec())
368			.map_or_else(|| 0, |asset| asset.amount)
369	}
370
371	fn get_plutus_data(&self) -> Option<PlutusData> {
372		(self.datum.as_ref())
373			.map(|datum| datum.bytes.clone())
374			.and_then(|bytes| PlutusData::from_bytes(bytes).ok())
375	}
376}
377
378/// Extension trait for [UtxoId].
379pub trait UtxoIdExt {
380	/// Converts domain [UtxoId] to CSL [TransactionInput].
381	fn to_csl(&self) -> TransactionInput;
382}
383
384impl UtxoIdExt for UtxoId {
385	fn to_csl(&self) -> TransactionInput {
386		TransactionInput::new(&TransactionHash::from(self.tx_hash.0), self.index.0.into())
387	}
388}
389
390#[derive(Clone)]
391pub(crate) struct TransactionContext {
392	/// This key is added as required signer and used to sign the transaction.
393	pub(crate) payment_key: CardanoPaymentSigningKey,
394	/// Used to pay for the transaction fees and uncovered transaction inputs
395	/// and as source of collateral inputs
396	pub(crate) payment_key_utxos: Vec<OgmiosUtxo>,
397	pub(crate) network: NetworkIdKind,
398	pub(crate) protocol_parameters: ProtocolParametersResponse,
399	pub(crate) change_address: Address,
400}
401
402impl TransactionContext {
403	/// Gets `TransactionContext`, having UTXOs for the given payment key and the network configuration,
404	/// required to perform most of the partner-chains smart contract operations.
405	pub(crate) async fn for_payment_key<C: QueryLedgerState + QueryNetwork>(
406		payment_key: &CardanoPaymentSigningKey,
407		client: &C,
408	) -> Result<TransactionContext, anyhow::Error> {
409		let payment_key = payment_key.clone();
410		let network = client.shelley_genesis_configuration().await?.network.to_csl();
411		let protocol_parameters = client.query_protocol_parameters().await?;
412		let payment_address = key_hash_address(&payment_key.0.to_public().hash(), network);
413		let payment_key_utxos = client.query_utxos(&[payment_address.to_bech32(None)?]).await?;
414		Ok(TransactionContext {
415			payment_key,
416			payment_key_utxos,
417			network,
418			protocol_parameters,
419			change_address: payment_address,
420		})
421	}
422
423	pub(crate) fn with_change_address(&self, change_address: &Address) -> Self {
424		Self {
425			payment_key: self.payment_key.clone(),
426			payment_key_utxos: self.payment_key_utxos.clone(),
427			network: self.network,
428			protocol_parameters: self.protocol_parameters.clone(),
429			change_address: change_address.clone(),
430		}
431	}
432
433	pub(crate) fn payment_key_hash(&self) -> Ed25519KeyHash {
434		self.payment_key.0.to_public().hash()
435	}
436
437	pub(crate) fn sign(&self, tx: &Transaction) -> Transaction {
438		let tx_hash: [u8; 32] = sidechain_domain::crypto::blake2b(tx.body().to_bytes().as_ref());
439		let signature = self.payment_key.0.sign(&tx_hash);
440		let mut witness_set = tx.witness_set();
441		let mut vkeywitnesses = witness_set.vkeys().unwrap_or_else(Vkeywitnesses::new);
442		vkeywitnesses
443			.add(&Vkeywitness::new(&Vkey::new(&self.payment_key.0.to_public()), &signature));
444		witness_set.set_vkeys(&vkeywitnesses);
445		Transaction::new(&tx.body(), &witness_set, tx.auxiliary_data())
446	}
447}
448
449pub(crate) trait OgmiosUtxosExt {
450	fn to_csl(&self) -> Result<TransactionUnspentOutputs, JsError>;
451}
452
453impl OgmiosUtxosExt for [OgmiosUtxo] {
454	fn to_csl(&self) -> Result<TransactionUnspentOutputs, JsError> {
455		let mut utxos = TransactionUnspentOutputs::new();
456		for utxo in self {
457			utxos.add(&utxo.to_csl()?);
458		}
459		Ok(utxos)
460	}
461}
462
463pub(crate) trait TransactionBuilderExt {
464	/// Creates output on the script address with datum that has 1 token with asset for the script and it has given datum attached.
465	fn add_output_with_one_script_token(
466		&mut self,
467		validator: &PlutusScript,
468		policy: &PlutusScript,
469		datum: &PlutusData,
470		ctx: &TransactionContext,
471	) -> Result<(), JsError>;
472
473	/// Adds ogmios inputs as collateral inputs to the tx builder.
474	fn add_collateral_inputs(
475		&mut self,
476		ctx: &TransactionContext,
477		inputs: &[OgmiosUtxo],
478	) -> Result<(), JsError>;
479
480	/// Adds minting of 1 token (with empty asset name) for the given script
481	fn add_mint_one_script_token(
482		&mut self,
483		script: &PlutusScript,
484		asset_name: &AssetName,
485		redeemer_data: &PlutusData,
486		ex_units: &ExUnits,
487	) -> Result<(), JsError>;
488
489	fn add_mint_script_tokens(
490		&mut self,
491		script: &PlutusScript,
492		asset_name: &AssetName,
493		redeemer_data: &PlutusData,
494		ex_units: &ExUnits,
495		amount: &Int,
496	) -> Result<(), JsError>;
497
498	/// Adds minting of tokens (with empty asset name) for the given script using reference input.
499	/// IMPORTANT: Because CSL doesn't properly calculate transaction fee if the script is Native,
500	/// this function adds reference input and regular native script, that is added to witnesses.
501	/// This native script has to be removed from witnesses, otherwise the transaction is rejected!
502	fn add_mint_script_token_using_reference_script(
503		&mut self,
504		script: &Script,
505		ref_input: &TransactionInput,
506		amount: &Int,
507		costs: &Costs,
508	) -> Result<(), JsError>;
509
510	/// Adds minting of 1 token (with empty asset name) for the given script using reference input.
511	/// IMPORTANT: Because CSL doesn't properly calculate transaction fee if the script is Native,
512	/// this function adds reference input and regular native script, that is added to witnesses.
513	/// This native script has to be removed from witnesses, otherwise the transaction is rejected!
514	fn add_mint_one_script_token_using_reference_script(
515		&mut self,
516		script: &Script,
517		ref_input: &TransactionInput,
518		costs: &Costs,
519	) -> Result<(), JsError> {
520		self.add_mint_script_token_using_reference_script(
521			script,
522			ref_input,
523			&Int::new_i32(1),
524			costs,
525		)
526	}
527
528	/// Sets fields required by the most of partner-chains smart contract transactions.
529	/// Uses input from `ctx` to cover already present outputs.
530	/// Adds collateral inputs using quite a simple algorithm.
531	fn balance_update_and_build(
532		&mut self,
533		ctx: &TransactionContext,
534	) -> Result<Transaction, JsError>;
535}
536
537impl TransactionBuilderExt for TransactionBuilder {
538	fn add_output_with_one_script_token(
539		&mut self,
540		validator: &PlutusScript,
541		policy: &PlutusScript,
542		datum: &PlutusData,
543		ctx: &TransactionContext,
544	) -> Result<(), JsError> {
545		let amount_builder = TransactionOutputBuilder::new()
546			.with_address(&validator.address(ctx.network))
547			.with_plutus_data(datum)
548			.next()?;
549		let ma = MultiAsset::new().with_asset_amount(&policy.empty_name_asset(), 1u64)?;
550		let output = amount_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()?;
551		self.add_output(&output)
552	}
553
554	fn add_collateral_inputs(
555		&mut self,
556		ctx: &TransactionContext,
557		inputs: &[OgmiosUtxo],
558	) -> Result<(), JsError> {
559		let mut collateral_builder = TxInputsBuilder::new();
560		for utxo in inputs.iter() {
561			collateral_builder.add_regular_input(
562				&key_hash_address(&ctx.payment_key_hash(), ctx.network),
563				&utxo.to_csl_tx_input(),
564				&utxo.value.to_csl()?,
565			)?;
566		}
567		self.set_collateral(&collateral_builder);
568		Ok(())
569	}
570
571	fn add_mint_one_script_token(
572		&mut self,
573		script: &PlutusScript,
574		asset_name: &AssetName,
575		redeemer_data: &PlutusData,
576		ex_units: &ExUnits,
577	) -> Result<(), JsError> {
578		let mut mint_builder = self.get_mint_builder().unwrap_or(MintBuilder::new());
579
580		let validator_source = PlutusScriptSource::new(&script.to_csl());
581		let mint_witness = MintWitness::new_plutus_script(
582			&validator_source,
583			&Redeemer::new(&RedeemerTag::new_mint(), &0u32.into(), redeemer_data, ex_units),
584		);
585		mint_builder.add_asset(&mint_witness, asset_name, &Int::new_i32(1))?;
586		self.set_mint_builder(&mint_builder);
587		Ok(())
588	}
589
590	fn add_mint_script_tokens(
591		&mut self,
592		script: &PlutusScript,
593		asset_name: &AssetName,
594		redeemer_data: &PlutusData,
595		ex_units: &ExUnits,
596		amount: &Int,
597	) -> Result<(), JsError> {
598		let mut mint_builder = self.get_mint_builder().unwrap_or(MintBuilder::new());
599
600		let validator_source = PlutusScriptSource::new(&script.to_csl());
601		let mint_witness = MintWitness::new_plutus_script(
602			&validator_source,
603			&Redeemer::new(&RedeemerTag::new_mint(), &0u32.into(), redeemer_data, ex_units),
604		);
605		mint_builder.add_asset(&mint_witness, asset_name, amount)?;
606		self.set_mint_builder(&mint_builder);
607		Ok(())
608	}
609
610	fn add_mint_script_token_using_reference_script(
611		&mut self,
612		script: &Script,
613		ref_input: &TransactionInput,
614		amount: &Int,
615		costs: &Costs,
616	) -> Result<(), JsError> {
617		let mut mint_builder = self.get_mint_builder().unwrap_or(MintBuilder::new());
618
619		match script {
620			Script::Plutus(script) => {
621				let source = PlutusScriptSource::new_ref_input(
622					&script.csl_script_hash(),
623					ref_input,
624					&script.language,
625					script.bytes.len(),
626				);
627				let mint_witness = MintWitness::new_plutus_script(
628					&source,
629					&Redeemer::new(
630						&RedeemerTag::new_mint(),
631						&0u32.into(),
632						&unit_plutus_data(),
633						&costs.get_mint(script),
634					),
635				);
636				mint_builder.add_asset(&mint_witness, &empty_asset_name(), amount)?;
637				self.set_mint_builder(&mint_builder);
638			},
639			Script::Native(script) => {
640				// new_ref_input causes invalid fee
641				let source = NativeScriptSource::new(script);
642				let mint_witness = MintWitness::new_native_script(&source);
643				mint_builder.add_asset(&mint_witness, &empty_asset_name(), amount)?;
644				self.set_mint_builder(&mint_builder);
645				self.add_reference_input(ref_input);
646			},
647		}
648		Ok(())
649	}
650
651	fn balance_update_and_build(
652		&mut self,
653		ctx: &TransactionContext,
654	) -> Result<Transaction, JsError> {
655		fn max_possible_collaterals(ctx: &TransactionContext) -> Vec<OgmiosUtxo> {
656			let mut utxos = ctx.payment_key_utxos.clone();
657			utxos.sort_by(|a, b| b.value.lovelace.cmp(&a.value.lovelace));
658			let max_inputs = ctx.protocol_parameters.max_collateral_inputs;
659			utxos
660				.into_iter()
661				.take(max_inputs.try_into().expect("max_collateral_input fit in usize"))
662				.collect()
663		}
664		// Tries to balance tx with given collateral inputs
665		fn try_balance(
666			builder: &mut TransactionBuilder,
667			collateral_inputs: &[OgmiosUtxo],
668			ctx: &TransactionContext,
669		) -> Result<Transaction, JsError> {
670			builder.add_required_signer(&ctx.payment_key_hash());
671			if collateral_inputs.is_empty() {
672				builder.add_inputs_from_and_change(
673					&ctx.payment_key_utxos.to_csl()?,
674					CoinSelectionStrategyCIP2::LargestFirstMultiAsset,
675					&ChangeConfig::new(&ctx.change_address),
676				)?;
677			} else {
678				builder.add_collateral_inputs(ctx, collateral_inputs)?;
679				builder.set_script_data_hash(&[0u8; 32].into());
680				// Fake script script data hash is required for proper fee computation
681				builder.add_inputs_from_and_change_with_collateral_return(
682					&ctx.payment_key_utxos.to_csl()?,
683					CoinSelectionStrategyCIP2::LargestFirstMultiAsset,
684					&ChangeConfig::new(&ctx.change_address),
685					&ctx.protocol_parameters.collateral_percentage.into(),
686				)?;
687				builder.calc_script_data_hash(&convert_cost_models(
688					&ctx.protocol_parameters.plutus_cost_models,
689				))?;
690			}
691			builder.build_tx()
692		}
693		// Tries if the largest UTXO is enough to cover collateral, if not, adds more UTXOs
694		// starting from the largest remaining.
695		let mut selected = vec![];
696		for input in max_possible_collaterals(ctx) {
697			let mut builder = self.clone();
698			// Check if the used inputs are enough
699			let result = try_balance(&mut builder, &selected, ctx);
700			if result.is_ok() {
701				return result;
702			}
703			selected.push(input);
704		}
705
706		let balanced_transaction =
707		try_balance(self, &selected, ctx)
708			.map_err(|e| JsError::from_str(&format!("Could not balance transaction. Usually it means that the payment key does not own UTXO set required to cover transaction outputs and fees or to provide collateral. Cause: {}", e)))?;
709
710		debug_assert!(
711			balanced_transaction.body().collateral().is_some(),
712			"BUG: Balanced transaction should have collateral set."
713		);
714		debug_assert!(
715			balanced_transaction.body().collateral_return().is_some(),
716			"BUG: Balanced transaction should have collateral returned."
717		);
718
719		Ok(balanced_transaction)
720	}
721}
722
723#[derive(Clone, Debug)]
724/// Type representing a Cardano script.
725pub enum Script {
726	/// Plutus script
727	Plutus(PlutusScript),
728	/// Native script
729	Native(NativeScript),
730}
731
732impl Script {
733	#[cfg(test)]
734	pub(crate) fn script_hash(&self) -> [u8; 28] {
735		match self {
736			Self::Plutus(script) => script.script_hash(),
737			Self::Native(script) => {
738				script.hash().to_bytes().try_into().expect("CSL script hash is always 28 bytes")
739			},
740		}
741	}
742}
743
744pub(crate) trait TransactionOutputAmountBuilderExt: Sized {
745	fn get_minimum_ada(&self, ctx: &TransactionContext) -> Result<BigNum, JsError>;
746	fn with_minimum_ada(self, ctx: &TransactionContext) -> Result<Self, JsError>;
747	fn with_minimum_ada_and_asset(
748		self,
749		ma: &MultiAsset,
750		ctx: &TransactionContext,
751	) -> Result<Self, JsError>;
752}
753
754impl TransactionOutputAmountBuilderExt for TransactionOutputAmountBuilder {
755	fn get_minimum_ada(&self, ctx: &TransactionContext) -> Result<BigNum, JsError> {
756		MinOutputAdaCalculator::new(
757			&self.build()?,
758			&DataCost::new_coins_per_byte(
759				&ctx.protocol_parameters.min_utxo_deposit_coefficient.into(),
760			),
761		)
762		.calculate_ada()
763	}
764
765	fn with_minimum_ada(self, ctx: &TransactionContext) -> Result<Self, JsError> {
766		let min_ada = self.with_coin(&0u64.into()).get_minimum_ada(ctx)?;
767		Ok(self.with_coin(&min_ada))
768	}
769
770	fn with_minimum_ada_and_asset(
771		self,
772		ma: &MultiAsset,
773		ctx: &TransactionContext,
774	) -> Result<Self, JsError> {
775		let min_ada = self.with_coin_and_asset(&0u64.into(), ma).get_minimum_ada(ctx)?;
776		Ok(self.with_coin_and_asset(&min_ada, ma))
777	}
778}
779
780pub(crate) trait InputsBuilderExt: Sized {
781	fn add_script_utxo_input(
782		&mut self,
783		utxo: &OgmiosUtxo,
784		script: &PlutusScript,
785		data: &PlutusData,
786		ex_units: &ExUnits,
787	) -> Result<(), JsError>;
788
789	/// Adds ogmios inputs to the tx inputs builder.
790	fn add_regular_inputs(&mut self, utxos: &[OgmiosUtxo]) -> Result<(), JsError>;
791
792	fn with_regular_inputs(utxos: &[OgmiosUtxo]) -> Result<Self, JsError>;
793}
794
795impl InputsBuilderExt for TxInputsBuilder {
796	fn add_script_utxo_input(
797		&mut self,
798		utxo: &OgmiosUtxo,
799		script: &PlutusScript,
800		data: &PlutusData,
801		ex_units: &ExUnits,
802	) -> Result<(), JsError> {
803		let input = utxo.to_csl_tx_input();
804		let amount = &utxo.value.to_csl()?;
805		let witness = PlutusWitness::new_without_datum(
806			&script.to_csl(),
807			&Redeemer::new(
808				&RedeemerTag::new_spend(),
809				// CSL will set redeemer index for the index of script input after sorting transaction inputs
810				&0u32.into(),
811				data,
812				ex_units,
813			),
814		);
815		self.add_plutus_script_input(&witness, &input, amount);
816		Ok(())
817	}
818
819	fn add_regular_inputs(&mut self, utxos: &[OgmiosUtxo]) -> Result<(), JsError> {
820		for utxo in utxos.iter() {
821			self.add_regular_utxo(&utxo.to_csl()?)?;
822		}
823		Ok(())
824	}
825
826	fn with_regular_inputs(utxos: &[OgmiosUtxo]) -> Result<Self, JsError> {
827		let mut tx_input_builder = Self::new();
828		tx_input_builder.add_regular_inputs(utxos)?;
829		Ok(tx_input_builder)
830	}
831}
832
833pub(crate) trait AssetNameExt: Sized {
834	fn to_csl(&self) -> Result<cardano_serialization_lib::AssetName, JsError>;
835	fn from_csl(asset_name: cardano_serialization_lib::AssetName) -> Result<Self, JsError>;
836}
837
838impl AssetNameExt for sidechain_domain::AssetName {
839	fn to_csl(&self) -> Result<cardano_serialization_lib::AssetName, JsError> {
840		cardano_serialization_lib::AssetName::new(self.0.to_vec())
841	}
842	fn from_csl(asset_name: cardano_serialization_lib::AssetName) -> Result<Self, JsError> {
843		let name = asset_name.name().try_into().map_err(|err| {
844			JsError::from_str(&format!("Failed to cast CSL asset name to domain: {err:?}"))
845		})?;
846		Ok(Self(name))
847	}
848}
849
850pub(crate) trait AssetIdExt {
851	fn to_multi_asset(&self, amount: impl Into<BigNum>) -> Result<MultiAsset, JsError>;
852}
853impl AssetIdExt for AssetId {
854	fn to_multi_asset(&self, amount: impl Into<BigNum>) -> Result<MultiAsset, JsError> {
855		let mut ma = MultiAsset::new();
856		let mut assets = Assets::new();
857		assets.insert(&self.asset_name.to_csl()?, &amount.into());
858		ma.insert(&self.policy_id.0.into(), &assets);
859		Ok(ma)
860	}
861}
862
863pub(crate) trait MultiAssetExt: Sized {
864	fn from_ogmios_utxo(utxo: &OgmiosUtxo) -> Result<Self, JsError>;
865	fn with_asset_amount(self, asset: &AssetId, amount: impl Into<BigNum>)
866	-> Result<Self, JsError>;
867}
868
869impl MultiAssetExt for MultiAsset {
870	fn from_ogmios_utxo(utxo: &OgmiosUtxo) -> Result<Self, JsError> {
871		let mut ma = MultiAsset::new();
872		for (policy, policy_assets) in utxo.value.native_tokens.iter() {
873			let mut assets = Assets::new();
874			for asset in policy_assets {
875				assets.insert(
876					&cardano_serialization_lib::AssetName::new(asset.name.clone())?,
877					&asset.amount.into(),
878				);
879			}
880			ma.insert(&PolicyID::from(*policy), &assets);
881		}
882		Ok(ma)
883	}
884	fn with_asset_amount(
885		mut self,
886		asset: &AssetId,
887		amount: impl Into<BigNum>,
888	) -> Result<Self, JsError> {
889		let policy_id = asset.policy_id.0.into();
890		let asset_name = asset.asset_name.to_csl()?;
891		let amount: BigNum = amount.into();
892		if amount > BigNum::zero() {
893			self.set_asset(&policy_id, &asset_name, &amount);
894			Ok(self)
895		} else {
896			// CSL doesn't have a public API to remove asset from MultiAsset, setting it to 0 isn't really helpful.
897			let current_value = self.get_asset(&policy_id, &asset_name);
898			if current_value > BigNum::zero() {
899				let ma_to_sub = MultiAsset::new().with_asset_amount(asset, current_value)?;
900				Ok(self.sub(&ma_to_sub))
901			} else {
902				Ok(self)
903			}
904		}
905	}
906}
907
908pub(crate) trait TransactionExt: Sized {
909	/// Removes all native scripts from transaction witness set.
910	fn remove_native_script_witnesses(self) -> Self;
911}
912
913impl TransactionExt for Transaction {
914	fn remove_native_script_witnesses(self) -> Self {
915		let ws = self.witness_set();
916		let mut new_ws = TransactionWitnessSet::new();
917		if let Some(bootstraps) = ws.bootstraps() {
918			new_ws.set_bootstraps(&bootstraps)
919		}
920		if let Some(plutus_data) = ws.plutus_data() {
921			new_ws.set_plutus_data(&plutus_data);
922		}
923		if let Some(plutus_scripts) = ws.plutus_scripts() {
924			new_ws.set_plutus_scripts(&plutus_scripts);
925		}
926		if let Some(redeemers) = ws.redeemers() {
927			new_ws.set_redeemers(&redeemers);
928		}
929		if let Some(vkeys) = ws.vkeys() {
930			new_ws.set_vkeys(&vkeys);
931		}
932		Transaction::new(&self.body(), &new_ws, self.auxiliary_data())
933	}
934}
935
936/// In Plutus smart-contracts, unit value is represented as `Constr 0 []`.
937/// It is used in many places where there is no particular value needed for redeemer.
938pub(crate) fn unit_plutus_data() -> PlutusData {
939	PlutusData::new_empty_constr_plutus_data(&BigNum::zero())
940}
941
942#[cfg(test)]
943mod tests {
944	use super::*;
945	use crate::plutus_script::PlutusScript;
946	use crate::test_values::protocol_parameters;
947	use cardano_serialization_lib::{AssetName, Language, NetworkIdKind};
948	use hex_literal::hex;
949	use ogmios_client::types::{Asset, OgmiosValue};
950	use pretty_assertions::assert_eq;
951
952	#[test]
953	fn candidates_script_address_test() {
954		let address = PlutusScript::from_cbor(
955			&crate::plutus_script::tests::CANDIDATES_SCRIPT_WITH_APPLIED_PARAMS,
956			Language::new_plutus_v2(),
957		)
958		.address(NetworkIdKind::Testnet);
959		assert_eq!(
960			address.to_bech32(None).unwrap(),
961			"addr_test1wpcsmvsxdjal5jxytvgd3hfntg9eav888mzfykukdjfcx2ce6knnp"
962		);
963	}
964
965	#[test]
966	fn payment_address_test() {
967		let address = payment_address(
968			&hex!("a35ef86f1622172816bb9e916aea86903b2c8d32c728ad5c9b9472be7e3c5e88"),
969			NetworkIdKind::Testnet,
970		);
971		assert_eq!(
972			address.to_bech32(None).unwrap(),
973			"addr_test1vqezxrh24ts0775hulcg3ejcwj7hns8792vnn8met6z9gwsxt87zy"
974		)
975	}
976
977	#[test]
978	fn linear_fee_test() {
979		let fee = super::linear_fee(&protocol_parameters());
980		assert_eq!(fee.constant(), 155381u32.into());
981		assert_eq!(fee.coefficient(), 44u32.into());
982	}
983
984	#[test]
985	fn ratio_to_unit_interval_test() {
986		let ratio = fraction::Ratio::new(577, 10000);
987		let unit_interval = super::ratio_to_unit_interval(&ratio);
988		assert_eq!(unit_interval.numerator(), 577u64.into());
989		assert_eq!(unit_interval.denominator(), 10000u64.into());
990	}
991
992	#[test]
993	fn convert_value_without_multi_asset_test() {
994		let ogmios_value = OgmiosValue::new_lovelace(1234567);
995		let value = &ogmios_value.to_csl().unwrap();
996		assert_eq!(value.coin(), 1234567u64.into());
997		assert_eq!(value.multiasset(), None);
998	}
999
1000	#[test]
1001	fn convert_value_with_multi_asset_test() {
1002		let ogmios_value = OgmiosValue {
1003			lovelace: 1234567,
1004			native_tokens: vec![
1005				([0u8; 28], vec![Asset { name: vec![], amount: 111 }]),
1006				(
1007					[1u8; 28],
1008					vec![
1009						Asset { name: hex!("222222").to_vec(), amount: 222 },
1010						Asset { name: hex!("333333").to_vec(), amount: 333 },
1011					],
1012				),
1013			]
1014			.into_iter()
1015			.collect(),
1016		};
1017		let value = &ogmios_value.to_csl().unwrap();
1018		assert_eq!(value.coin(), 1234567u64.into());
1019		let multiasset = value.multiasset().unwrap();
1020		assert_eq!(
1021			multiasset.get_asset(&[0u8; 28].into(), &AssetName::new(vec![]).unwrap()),
1022			111u64.into()
1023		);
1024		assert_eq!(
1025			multiasset
1026				.get_asset(&[1u8; 28].into(), &AssetName::new(hex!("222222").to_vec()).unwrap()),
1027			222u64.into()
1028		);
1029		assert_eq!(
1030			multiasset
1031				.get_asset(&[1u8; 28].into(), &AssetName::new(hex!("333333").to_vec()).unwrap()),
1032			333u64.into()
1033		);
1034	}
1035
1036	#[test]
1037	fn convert_cost_models_test() {
1038		let cost_models = super::convert_cost_models(&protocol_parameters().plutus_cost_models);
1039		assert_eq!(cost_models.keys().len(), 3);
1040		assert_eq!(
1041			cost_models
1042				.get(&Language::new_plutus_v1())
1043				.unwrap()
1044				.get(0)
1045				.unwrap()
1046				.as_i32_or_nothing()
1047				.unwrap(),
1048			898148
1049		);
1050		assert_eq!(
1051			cost_models
1052				.get(&Language::new_plutus_v2())
1053				.unwrap()
1054				.get(1)
1055				.unwrap()
1056				.as_i32_or_nothing()
1057				.unwrap(),
1058			10
1059		);
1060		assert_eq!(
1061			cost_models
1062				.get(&Language::new_plutus_v3())
1063				.unwrap()
1064				.get(0)
1065				.unwrap()
1066				.as_i32_or_nothing()
1067				.unwrap(),
1068			-900
1069		);
1070	}
1071
1072	#[test]
1073	fn ogmios_utxo_to_csl_with_plutus_script_attached() {
1074		let json = serde_json::json!(
1075		{
1076		   "transaction": {
1077			 "id": "1fd4a3df3e0bd48dd189878bc8e4d7419fea24c8669c84019609c897adc40f09"
1078		   },
1079		   "index": 0,
1080		   "address": "addr_test1vq0sjaaupatuvl9x6aefdsd4whlqtfku93068qzkhf3u2rqt9cnuq",
1081		   "value": {
1082			 "ada": {
1083			   "lovelace": 8904460
1084			 }
1085		   },
1086		   "script": {
1087			 "language": "plutus:v2",
1088			 "cbor": "59072301000033233223222253232335332232353232325333573466e1d20000021323232323232332212330010030023232325333573466e1d2000002132323232323232323232332323233323333323332332332222222222221233333333333300100d00c00b00a00900800700600500400300230013574202460026ae84044c00c8c8c8c94ccd5cd19b87480000084cc8848cc00400c008c070d5d080098029aba135744002260489201035054310035573c0046aae74004dd5000998018009aba100f23232325333573466e1d20000021323232333322221233330010050040030023232325333573466e1d20000021332212330010030023020357420026600803e6ae84d5d100089814a481035054310035573c0046aae74004dd51aba1004300835742006646464a666ae68cdc3a4000004224440062a666ae68cdc3a4004004264244460020086eb8d5d08008a999ab9a3370e9002001099091118010021aba100113029491035054310035573c0046aae74004dd51aba10023300175c6ae84d5d1001111919192999ab9a3370e900100108910008a999ab9a3370e9000001099091180100198029aba10011302a491035054310035573c0046aae74004dd50009aba20013574400226046921035054310035573c0046aae74004dd500098009aba100d30013574201860046004eb4cc00404cd5d080519980200a3ad35742012646464a666ae68cdc3a40000042646466442466002006004646464a666ae68cdc3a40000042664424660020060046600aeb4d5d080098021aba1357440022604c921035054310035573c0046aae74004dd51aba10033232325333573466e1d20000021332212330010030023300575a6ae84004c010d5d09aba2001130264901035054310035573c0046aae74004dd51aba1357440064646464a666ae68cdc3a400000420482a666ae68cdc3a4004004204a2604c921035054310035573c0046aae74004dd5000911919192999ab9a3370e9000001089110010a999ab9a3370e90010010990911180180218029aba100115333573466e1d20040021122200113026491035054310035573c0046aae74004dd500089810a49035054310035573c0046aae74004dd51aba10083300175c6ae8401c8c88c008dd60009813111999aab9f0012028233502730043574200460066ae88008084ccc00c044008d5d0802998008011aba1004300275c40024464460046eac004c09088cccd55cf800901311919a8131991091980080180118031aab9d001300535573c00260086ae8800cd5d080100f98099aba1357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae88004d5d100089808249035054310035573c0046aae74004dd51aba10073001357426ae8801c8c8c8c94ccd5cd19b87480000084c848888c00c014dd71aba100115333573466e1d20020021321222230010053008357420022a666ae68cdc3a400800426424444600400a600c6ae8400454ccd5cd19b87480180084c848888c010014c014d5d080089808249035054310035573c0046aae74004dd500091919192999ab9a3370e900000109909111111180280418029aba100115333573466e1d20020021321222222230070083005357420022a666ae68cdc3a400800426644244444446600c012010600a6ae84004dd71aba1357440022a666ae68cdc3a400c0042664424444444660040120106eb8d5d08009bae357426ae8800454ccd5cd19b87480200084cc8848888888cc004024020dd71aba1001375a6ae84d5d10008a999ab9a3370e90050010891111110020a999ab9a3370e900600108911111100189807a49035054310035573c0046aae74004dd500091919192999ab9a3370e9000001099091180100198029aba100115333573466e1d2002002132333222122333001005004003375a6ae84008dd69aba1001375a6ae84d5d10009aba20011300e4901035054310035573c0046aae74004dd500091919192999ab9a3370e900000109909118010019bae357420022a666ae68cdc3a400400426424460020066eb8d5d080089806a481035054310035573c0046aae74004dd500091919192999ab9a3370e900000109991091980080180118029aba1001375a6ae84d5d1000898062481035054310035573c0046aae74004dd500091919192999ab9a3370e900000109bae3574200226016921035054310035573c0046aae74004dd500089803a49035054310035573c0046aae74004dd5003111999a8009002919199ab9a337126602044a66a002290001109a801112999ab9a3371e004010260260022600c006600244444444444401066e0ccdc09a9a980091111111111100291001112999a80110a99a80108008b0b0b002a4181520e00e00ca006400a400a6eb401c48800848800440084c00524010350543500232633573800200424002600644a66a002290001109a8011119b800013006003122002122122330010040032323001001223300330020020014c01051a67998a9b0001"
1089		   }
1090		 });
1091
1092		let ogmios_utxo: OgmiosUtxo = serde_json::from_value(json).unwrap();
1093		ogmios_utxo.to_csl().unwrap();
1094	}
1095
1096	#[test]
1097	fn ogmios_utxo_to_csl_with_native_script_attached() {
1098		let json = serde_json::json!(
1099				{
1100		  "transaction": {
1101			"id": "57342ce4f30afa749bd78f0c093609366d997a1c4747d206ec7fd0aea9a35b55"
1102		  },
1103		  "index": 0,
1104		  "address": "addr_test1wplvesjjxtg8lhyy34ak2dr9l3kz8ged3hajvcvpanfx7rcwzvtc5",
1105		  "value": {
1106			"ada": {
1107			  "lovelace": 1430920
1108			},
1109			"ab81fe48f392989bd215f9fdc25ece3335a248696b2a64abc1acb595": {
1110			  "56657273696f6e206f7261636c65": 1
1111			}
1112		  },
1113		  "datum": "9f1820581cab81fe48f392989bd215f9fdc25ece3335a248696b2a64abc1acb595ff",
1114		  "script": {
1115			"language": "native",
1116			"json": {
1117			  "clause": "some",
1118			  "atLeast": 1,
1119			  "from": [
1120				{
1121				  "clause": "signature",
1122				  "from": "e8c300330fe315531ca89d4a2e7d0c80211bc70b473b1ed4979dff2b"
1123				}
1124			  ]
1125			},
1126			"cbor": "830301818200581ce8c300330fe315531ca89d4a2e7d0c80211bc70b473b1ed4979dff2b"
1127		  }
1128		});
1129
1130		let ogmios_utxo: OgmiosUtxo = serde_json::from_value(json).unwrap();
1131		ogmios_utxo.to_csl().unwrap();
1132	}
1133}
1134
1135#[cfg(test)]
1136mod prop_tests {
1137	use super::{
1138		OgmiosUtxoExt, TransactionBuilderExt, TransactionContext, empty_asset_name,
1139		get_builder_config, unit_plutus_data, zero_ex_units,
1140	};
1141	use crate::test_values::*;
1142	use cardano_serialization_lib::{
1143		NetworkIdKind, Transaction, TransactionBuilder, TransactionInputs, TransactionOutput, Value,
1144	};
1145	use ogmios_client::types::OgmiosValue;
1146	use ogmios_client::types::{OgmiosTx, OgmiosUtxo};
1147	use proptest::{
1148		array::uniform32,
1149		collection::{hash_set, vec},
1150		prelude::*,
1151	};
1152	use sidechain_domain::{McTxHash, UtxoId, UtxoIndex};
1153
1154	const MIN_UTXO_LOVELACE: u64 = 1000000;
1155	const FIVE_ADA: u64 = 5000000;
1156
1157	fn multi_asset_transaction_balancing_test(payment_utxos: Vec<OgmiosUtxo>) {
1158		let ctx = TransactionContext {
1159			payment_key: payment_key(),
1160			payment_key_utxos: payment_utxos.clone(),
1161			network: NetworkIdKind::Testnet,
1162			protocol_parameters: protocol_parameters(),
1163			change_address: payment_addr(),
1164		};
1165		let mut tx_builder = TransactionBuilder::new(&get_builder_config(&ctx).unwrap());
1166		tx_builder
1167			.add_mint_one_script_token(
1168				&test_policy(),
1169				&empty_asset_name(),
1170				&unit_plutus_data(),
1171				&zero_ex_units(),
1172			)
1173			.unwrap();
1174		tx_builder
1175			.add_output_with_one_script_token(
1176				&test_validator(),
1177				&test_policy(),
1178				&test_plutus_data(),
1179				&ctx,
1180			)
1181			.unwrap();
1182
1183		let tx = tx_builder.balance_update_and_build(&ctx).unwrap();
1184
1185		used_inputs_lovelace_equals_outputs_and_fee(&tx, &payment_utxos);
1186		selected_collateral_inputs_equal_total_collateral_and_collateral_return(&tx, payment_utxos);
1187		fee_is_less_than_one_and_half_ada(&tx);
1188	}
1189
1190	fn ada_only_transaction_balancing_test(payment_utxos: Vec<OgmiosUtxo>) {
1191		let ctx = TransactionContext {
1192			payment_key: payment_key(),
1193			payment_key_utxos: payment_utxos.clone(),
1194			network: NetworkIdKind::Testnet,
1195			protocol_parameters: protocol_parameters(),
1196			change_address: payment_addr(),
1197		};
1198		let mut tx_builder = TransactionBuilder::new(&get_builder_config(&ctx).unwrap());
1199		tx_builder
1200			.add_output(&TransactionOutput::new(&payment_addr(), &Value::new(&1500000u64.into())))
1201			.unwrap();
1202
1203		let tx = tx_builder.balance_update_and_build(&ctx).unwrap();
1204
1205		used_inputs_lovelace_equals_outputs_and_fee(&tx, &payment_utxos);
1206		there_is_no_collateral(&tx);
1207		fee_is_less_than_one_and_half_ada(&tx);
1208	}
1209
1210	fn used_inputs_lovelace_equals_outputs_and_fee(tx: &Transaction, payment_utxos: &[OgmiosUtxo]) {
1211		let used_inputs: Vec<OgmiosUtxo> = match_inputs(&tx.body().inputs(), payment_utxos);
1212		let used_inputs_value: u64 = sum_lovelace(&used_inputs);
1213		let outputs_lovelace_sum: u64 = tx
1214			.body()
1215			.outputs()
1216			.into_iter()
1217			.map(|output| {
1218				let value: u64 = output.amount().coin().into();
1219				value
1220			})
1221			.sum();
1222		let fee: u64 = tx.body().fee().into();
1223		// Used inputs are qual to the sum of the outputs plus the fee
1224		assert_eq!(used_inputs_value, outputs_lovelace_sum + fee);
1225	}
1226
1227	fn selected_collateral_inputs_equal_total_collateral_and_collateral_return(
1228		tx: &Transaction,
1229		payment_utxos: Vec<OgmiosUtxo>,
1230	) {
1231		let collateral_inputs_sum: u64 =
1232			sum_lovelace(&match_inputs(&tx.body().collateral().unwrap(), &payment_utxos));
1233		let collateral_return: u64 = tx.body().collateral_return().unwrap().amount().coin().into();
1234		let total_collateral: u64 = tx.body().total_collateral().unwrap().into();
1235		assert_eq!(collateral_inputs_sum, collateral_return + total_collateral);
1236	}
1237
1238	// Exact fee depends on inputs and outputs, but it definately is less than 1.5 ADA
1239	fn fee_is_less_than_one_and_half_ada(tx: &Transaction) {
1240		assert!(tx.body().fee() <= 1500000u64.into());
1241	}
1242
1243	fn there_is_no_collateral(tx: &Transaction) {
1244		assert!(tx.body().total_collateral().is_none());
1245		assert!(tx.body().collateral_return().is_none());
1246		assert!(tx.body().collateral().is_none())
1247	}
1248
1249	fn match_inputs(inputs: &TransactionInputs, payment_utxos: &[OgmiosUtxo]) -> Vec<OgmiosUtxo> {
1250		inputs
1251			.into_iter()
1252			.map(|input| {
1253				payment_utxos
1254					.iter()
1255					.find(|utxo| utxo.to_csl_tx_input() == *input)
1256					.unwrap()
1257					.clone()
1258			})
1259			.collect()
1260	}
1261
1262	fn sum_lovelace(utxos: &[OgmiosUtxo]) -> u64 {
1263		utxos.iter().map(|utxo| utxo.value.lovelace).sum()
1264	}
1265
1266	proptest! {
1267		#[test]
1268		fn balance_tx_with_minted_token(payment_utxos in arb_payment_utxos(10)
1269			.prop_filter("Inputs total lovelace too low", |utxos| sum_lovelace(utxos) > 4000000)) {
1270			multi_asset_transaction_balancing_test(payment_utxos)
1271		}
1272
1273		#[test]
1274		fn balance_tx_with_ada_only_token(payment_utxos in arb_payment_utxos(10)
1275			.prop_filter("Inputs total lovelace too low", |utxos| sum_lovelace(utxos) > 3000000)) {
1276			ada_only_transaction_balancing_test(payment_utxos)
1277		}
1278	}
1279
1280	prop_compose! {
1281		// Set is needed to be used, because we have to avoid UTXOs with the same id.
1282		fn arb_payment_utxos(n: usize)
1283			(utxo_ids in hash_set(arb_utxo_id(), 1..n))
1284			(utxo_ids in Just(utxo_ids.clone()), values in vec(arb_utxo_lovelace(), utxo_ids.len())
1285		) -> Vec<OgmiosUtxo> {
1286			utxo_ids.into_iter().zip(values.into_iter()).map(|(utxo_id, value)| OgmiosUtxo {
1287				transaction: OgmiosTx { id: utxo_id.tx_hash.0 },
1288				index: utxo_id.index.0,
1289				value,
1290				address: PAYMENT_ADDR.into(),
1291				..Default::default()
1292			}).collect()
1293		}
1294	}
1295
1296	prop_compose! {
1297		fn arb_utxo_lovelace()(value in MIN_UTXO_LOVELACE..FIVE_ADA) -> OgmiosValue {
1298			OgmiosValue::new_lovelace(value)
1299		}
1300	}
1301
1302	prop_compose! {
1303		fn arb_utxo_id()(tx_hash in uniform32(0u8..255u8), index in any::<u16>()) -> UtxoId {
1304			UtxoId {
1305				tx_hash: McTxHash(tx_hash),
1306				index: UtxoIndex(index),
1307			}
1308		}
1309	}
1310}