partner_chains_cardano_offchain/bridge/
deposit.rs

1//! Bridge transaction deposits an UTXO that contains the specified token at the IlliquidCirculationSupplyValidator.
2//! Such transaction can either be simple deposit or can involve consuming exactly one UTXO from
3//! the ICS Validator that has special token (for some reason called 'auth token').
4//! The former increases the number of UTXOs at ICS Validator, but the latter preserve it.
5//! Arbitrary number of UTXOs that does not have 'auth token' could be consumed, to decrese the number
6//! of UTXOs, but it is not implemented yet.
7
8use crate::{
9	TokenAmount,
10	await_tx::AwaitTx,
11	bridge::select_utxo_to_spend,
12	cardano_keys::CardanoPaymentSigningKey,
13	csl::{
14		CostStore, Costs, MultiAssetExt, OgmiosUtxoExt, TransactionBuilderExt, TransactionContext,
15		TransactionOutputAmountBuilderExt, get_builder_config,
16	},
17};
18use cardano_serialization_lib::{
19	Address, AssetName, BigNum, MultiAsset, PlutusData, ScriptHash, Transaction,
20	TransactionBuilder, TransactionOutputBuilder, TxInputsBuilder,
21};
22use ogmios_client::{
23	query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
24	query_network::QueryNetwork,
25	transactions::Transactions,
26	types::OgmiosUtxo,
27};
28use partner_chains_plutus_data::bridge::TokenTransferDatumV1;
29use sidechain_domain::{AssetId, McTxHash, UtxoId, byte_string::ByteString};
30use std::num::NonZero;
31
32use super::{ICSData, add_ics_utxo_input_with_validator_script_reference};
33
34/// This function deposits bridge token to the Illiquid Circulation Supply from the payment wallet.
35/// It does not consume existing UTXO at the validator address.
36///  - `genesis_utxo`: UTxO identifying the Partner Chain.
37///  - `amount` number of tokens to be deposited.
38///  - `pc_address` information to partner chain node, where to transfer funds
39///  - `payment_signing_key`: Signing key of the party paying fees.
40///  - `await_tx`: [AwaitTx] strategy.
41pub async fn deposit_without_ics_input<
42	C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
43	A: AwaitTx,
44>(
45	genesis_utxo: UtxoId,
46	token: AssetId,
47	amount: NonZero<u64>,
48	pc_address: &[u8],
49	payment_signing_key: &CardanoPaymentSigningKey,
50	client: &C,
51	await_tx: &A,
52) -> anyhow::Result<McTxHash> {
53	let ctx = TransactionContext::for_payment_key(payment_signing_key, client).await?;
54	let scripts = crate::scripts_data::get_scripts_data(genesis_utxo, ctx.network)?;
55	let ics_address =
56		Address::from_bech32(&scripts.addresses.illiquid_circulation_supply_validator)?;
57	let tx_hash = submit_deposit_only_tx(
58		&ics_address,
59		TokenAmount { token, amount: amount.get() },
60		pc_address,
61		&ctx,
62		client,
63		await_tx,
64	)
65	.await?;
66	Ok(tx_hash)
67}
68
69async fn submit_deposit_only_tx<
70	C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
71	A: AwaitTx,
72>(
73	ics_address: &Address,
74	amount: TokenAmount,
75	pc_address: &[u8],
76	ctx: &TransactionContext,
77	client: &C,
78	await_tx: &A,
79) -> anyhow::Result<McTxHash> {
80	let tx = deposit_only_tx(ics_address, amount, pc_address, ctx)?;
81	let signed_tx = ctx.sign(&tx).to_bytes();
82	let res = client.submit_transaction(&signed_tx).await.map_err(|e| {
83		anyhow::anyhow!(
84			"Bridge transfer transaction request failed: {}, tx bytes: {}",
85			e,
86			hex::encode(signed_tx)
87		)
88	})?;
89	let tx_id = res.transaction.id;
90	log::info!("Bridge transfer transaction submitted: {}", hex::encode(tx_id));
91	await_tx.await_tx_output(client, McTxHash(tx_id)).await?;
92	Ok(McTxHash(tx_id))
93}
94
95fn deposit_only_tx(
96	ics_address: &Address,
97	token_amount: TokenAmount,
98	pc_address: &[u8],
99	ctx: &TransactionContext,
100) -> anyhow::Result<Transaction> {
101	let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
102	let output_builder = TransactionOutputBuilder::new()
103		.with_address(ics_address)
104		.with_plutus_data(&to_user_transfer_datum(pc_address))
105		.next()?;
106	let ma = MultiAsset::new().with_asset_amount(&token_amount.token, token_amount.amount)?;
107	let output = output_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()?;
108	tx_builder.add_output(&output)?;
109	Ok(tx_builder.balance_update_and_build(ctx)?)
110}
111
112/// This function deposits bridge token to the Illiquid Circulation Supply from the payment wallet.
113/// It does spend existing UTXO (containing 'auth token') at the validator address.
114///  - `genesis_utxo`: UTxO identifying the Partner Chain.
115///  - `amount` number of tokens to be deposited.
116///  - `pc_address` information to partner chain node, where to transfer funds
117///  - `payment_signing_key`: Signing key of the party paying fees.
118///  - `await_tx`: [AwaitTx] strategy.
119pub async fn deposit_with_ics_spend<
120	C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
121	A: AwaitTx,
122>(
123	genesis_utxo: UtxoId,
124	token: AssetId,
125	amount: NonZero<u64>,
126	pc_address: &[u8],
127	payment_signing_key: &CardanoPaymentSigningKey,
128	client: &C,
129	await_tx: &A,
130) -> anyhow::Result<McTxHash> {
131	let ctx = TransactionContext::for_payment_key(payment_signing_key, client).await?;
132	let scripts = crate::scripts_data::get_scripts_data(genesis_utxo, ctx.network)?;
133	let ics_data = ICSData::get(genesis_utxo, &ctx, client).await?;
134	let token_amount = TokenAmount { token, amount: amount.get() };
135	let ics_address =
136		Address::from_bech32(&scripts.addresses.illiquid_circulation_supply_validator)?;
137	let ics_utxos = ics_data.get_validator_utxos_with_auth_token(&ctx, client).await?;
138	let ics_utxo_to_spend = select_utxo_to_spend(&ics_utxos, &ctx).ok_or(anyhow::anyhow!(
139		"Cannot find UTXOs with an 'auth token' at ICS Validator! Use simple deposit instead."
140	))?;
141	let tx_hash = submit_tx(
142		&ics_address,
143		&ics_utxo_to_spend,
144		&ics_data,
145		token_amount,
146		pc_address,
147		&ctx,
148		client,
149		await_tx,
150	)
151	.await?;
152	Ok(tx_hash)
153}
154
155async fn submit_tx<
156	C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
157	A: AwaitTx,
158>(
159	ics_address: &Address,
160	ics_utxo: &OgmiosUtxo,
161	ics_data: &ICSData,
162	amount: TokenAmount,
163	pc_address: &[u8],
164	ctx: &TransactionContext,
165	client: &C,
166	await_tx: &A,
167) -> anyhow::Result<McTxHash> {
168	let tx = Costs::calculate_costs(
169		|costs| deposit_tx(ics_address, ics_utxo, ics_data, amount.clone(), pc_address, ctx, costs),
170		client,
171	)
172	.await?;
173
174	let signed_tx = ctx.sign(&tx).to_bytes();
175	let res = client.submit_transaction(&signed_tx).await.map_err(|e| {
176		anyhow::anyhow!(
177			"Bridge transfer transaction request failed: {}, tx bytes: {}",
178			e,
179			hex::encode(signed_tx)
180		)
181	})?;
182	let tx_id = res.transaction.id;
183	log::info!("Bridge transfer transaction submitted: {}", hex::encode(tx_id));
184	await_tx.await_tx_output(client, McTxHash(tx_id)).await?;
185	Ok(McTxHash(tx_id))
186}
187
188fn deposit_tx(
189	ics_address: &Address,
190	ics_utxo: &OgmiosUtxo,
191	ics_data: &ICSData,
192	token_amount: TokenAmount,
193	pc_address: &[u8],
194	ctx: &TransactionContext,
195	costs: Costs,
196) -> anyhow::Result<Transaction> {
197	let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
198	let output_builder = TransactionOutputBuilder::new()
199		.with_address(ics_address)
200		.with_plutus_data(&to_user_transfer_datum(pc_address))
201		.next()?;
202	let mut ma = ics_utxo
203		.to_csl()
204		.unwrap()
205		.output()
206		.amount()
207		.multiasset()
208		.expect("ics_utxo has at least 'auth token'");
209	let policy_id = ScriptHash::from(token_amount.token.policy_id.0);
210	let mut assets = ma.get(&policy_id).unwrap_or_default();
211	let asset_name = AssetName::new(token_amount.token.asset_name.0.to_vec())
212		.expect("asset name that comes from ogmios is valid");
213	let amount = assets.get(&asset_name).unwrap_or_default();
214	assets.insert(&asset_name, &amount.checked_add(&BigNum::from(token_amount.amount))?);
215	let _ = ma.insert(&policy_id, &assets);
216	let output = output_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()?;
217	tx_builder.add_output(&output)?;
218
219	let mut inputs = TxInputsBuilder::new();
220	add_ics_utxo_input_with_validator_script_reference(
221		&mut inputs,
222		ics_utxo,
223		ics_data,
224		&costs.get_one_spend(),
225	)?;
226	tx_builder.set_inputs(&inputs);
227
228	tx_builder.add_script_reference_input(
229		&ics_data.validator_version_utxo.to_csl_tx_input(),
230		ics_data.scripts.validator.bytes.len(),
231	);
232	tx_builder.add_script_reference_input(
233		&ics_data.auth_policy_version_utxo.to_csl_tx_input(),
234		ics_data.scripts.auth_policy.bytes.len(),
235	);
236
237	Ok(tx_builder.balance_update_and_build(ctx)?)
238}
239
240fn to_user_transfer_datum(pc_address: &[u8]) -> PlutusData {
241	TokenTransferDatumV1::UserTransfer { receiver: ByteString(pc_address.to_vec()) }.into()
242}