partner_chains_cardano_offchain/bridge/
deposit.rs1use 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
34pub 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
112pub 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}