partner_chains_cardano_offchain/d_param/
mod.rs

1//! D-parameter is stored on chain in an UTXO at the D-parameter validator address.
2//! There should be at most one UTXO at the validator address and it should contain the D-parameter.
3//! This UTXO should have 1 token of the D-parameter policy with an empty asset name.
4//! The datum encodes D-parameter using VersionedGenericDatum envelope with the D-parameter being
5//! `datum` field being `[num_permissioned_candidates, num_registered_candidates]`.
6
7use crate::await_tx::AwaitTx;
8use crate::cardano_keys::CardanoPaymentSigningKey;
9use crate::csl::{
10	CostStore, Costs, InputsBuilderExt, NetworkTypeExt, TransactionBuilderExt, TransactionContext,
11	TransactionExt, empty_asset_name, get_builder_config, unit_plutus_data,
12};
13use crate::governance::GovernanceData;
14use crate::multisig::submit_or_create_tx_to_sign;
15use crate::multisig::{
16	MultiSigSmartContractResult, MultiSigSmartContractResult::TransactionSubmitted,
17};
18use crate::plutus_script::PlutusScript;
19use anyhow::anyhow;
20use cardano_serialization_lib::{PlutusData, Transaction, TransactionBuilder, TxInputsBuilder};
21use ogmios_client::query_ledger_state::QueryUtxoByUtxoId;
22use ogmios_client::{
23	query_ledger_state::QueryLedgerState, query_network::QueryNetwork, transactions::Transactions,
24	types::OgmiosUtxo,
25};
26use partner_chains_plutus_data::d_param::{DParamDatum, d_parameter_to_plutus_data};
27use sidechain_domain::{DParameter, UtxoId};
28
29#[cfg(test)]
30mod tests;
31
32/// This function upserts D-param.
33/// Arguments:
34///  - `genesis_utxo`: UTxO identifying the Partner Chain.
35///  - `d_parameter`: [DParameter] to be upserted.
36///  - `payment_signing_key`: Signing key of the party paying fees.
37///  - `await_tx`: [AwaitTx] strategy.
38pub async fn upsert_d_param<
39	C: QueryLedgerState + QueryNetwork + Transactions + QueryUtxoByUtxoId,
40	A: AwaitTx,
41>(
42	genesis_utxo: UtxoId,
43	d_parameter: &DParameter,
44	payment_signing_key: &CardanoPaymentSigningKey,
45	client: &C,
46	await_tx: &A,
47) -> anyhow::Result<Option<MultiSigSmartContractResult>> {
48	let ctx = TransactionContext::for_payment_key(payment_signing_key, client).await?;
49	let scripts = crate::scripts_data::d_parameter_scripts(genesis_utxo, ctx.network)?;
50	let validator_utxos = client.query_utxos(&[scripts.validator_address.clone()]).await?;
51
52	let tx_hash_opt = match get_current_d_parameter(validator_utxos)? {
53		Some((_, current_d_param)) if current_d_param == *d_parameter => {
54			log::info!("Current D-parameter value is equal to the one to be set.");
55			None
56		},
57		Some((current_utxo, _)) => {
58			log::info!("Current D-parameter is different to the one to be set. Updating.");
59			Some(
60				update_d_param(
61					&scripts.validator,
62					&scripts.policy,
63					d_parameter,
64					&current_utxo,
65					ctx,
66					genesis_utxo,
67					client,
68					await_tx,
69				)
70				.await?,
71			)
72		},
73		None => {
74			log::info!("There is no D-parameter set. Inserting new one.");
75			Some(
76				insert_d_param(
77					&scripts.validator,
78					&scripts.policy,
79					d_parameter,
80					ctx,
81					genesis_utxo,
82					client,
83					await_tx,
84				)
85				.await?,
86			)
87		},
88	};
89	if let Some(TransactionSubmitted(tx_hash)) = tx_hash_opt {
90		await_tx.await_tx_output(client, UtxoId::new(tx_hash.0, 0)).await?;
91	}
92	Ok(tx_hash_opt)
93}
94
95fn get_current_d_parameter(
96	validator_utxos: Vec<OgmiosUtxo>,
97) -> Result<Option<(OgmiosUtxo, DParameter)>, anyhow::Error> {
98	if let Some(utxo) = validator_utxos.first() {
99		let datum = utxo.datum.clone().ok_or_else(|| {
100			anyhow!("Invalid state: an UTXO at the validator script address does not have a datum")
101		})?;
102		let datum_plutus_data = PlutusData::from_bytes(datum.bytes).map_err(|e| {
103			anyhow!("Internal error: could not decode datum of D-parameter validator script: {}", e)
104		})?;
105		let current_d_param: DParameter = DParamDatum::try_from(datum_plutus_data)
106			.map_err(|e| {
107				anyhow!(
108					"Internal error: could not decode datum of D-parameter validator script: {}",
109					e
110				)
111			})?
112			.into();
113		Ok(Some((utxo.clone(), current_d_param)))
114	} else {
115		Ok(None)
116	}
117}
118
119async fn insert_d_param<
120	C: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
121	A: AwaitTx,
122>(
123	validator: &PlutusScript,
124	policy: &PlutusScript,
125	d_parameter: &DParameter,
126	ctx: TransactionContext,
127	genesis_utxo: UtxoId,
128	client: &C,
129	await_tx: &A,
130) -> anyhow::Result<MultiSigSmartContractResult> {
131	let gov_data = GovernanceData::get(genesis_utxo, client).await?;
132
133	submit_or_create_tx_to_sign(
134		&gov_data,
135		ctx,
136		|costs, ctx| mint_d_param_token_tx(validator, policy, d_parameter, &gov_data, costs, &ctx),
137		"Insert D-parameter",
138		client,
139		await_tx,
140	)
141	.await
142}
143
144async fn update_d_param<
145	C: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
146	A: AwaitTx,
147>(
148	validator: &PlutusScript,
149	policy: &PlutusScript,
150	d_parameter: &DParameter,
151	current_utxo: &OgmiosUtxo,
152	ctx: TransactionContext,
153	genesis_utxo: UtxoId,
154	client: &C,
155	await_tx: &A,
156) -> anyhow::Result<MultiSigSmartContractResult> {
157	let governance_data = GovernanceData::get(genesis_utxo, client).await?;
158
159	submit_or_create_tx_to_sign(
160		&governance_data,
161		ctx,
162		|costs, ctx| {
163			update_d_param_tx(
164				validator,
165				policy,
166				d_parameter,
167				current_utxo,
168				&governance_data,
169				costs,
170				&ctx,
171			)
172		},
173		"Update D-parameter",
174		client,
175		await_tx,
176	)
177	.await
178}
179
180fn mint_d_param_token_tx(
181	validator: &PlutusScript,
182	policy: &PlutusScript,
183	d_parameter: &DParameter,
184	governance_data: &GovernanceData,
185	costs: Costs,
186	ctx: &TransactionContext,
187) -> anyhow::Result<Transaction> {
188	let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
189	// The essence of transaction: mint D-Param token and set output with it, mint a governance token.
190	tx_builder.add_mint_one_script_token(
191		policy,
192		&empty_asset_name(),
193		&unit_plutus_data(),
194		&costs.get_mint(&policy.clone()),
195	)?;
196	tx_builder.add_output_with_one_script_token(
197		validator,
198		policy,
199		&d_parameter_to_plutus_data(d_parameter),
200		ctx,
201	)?;
202
203	let gov_tx_input = governance_data.utxo_id_as_tx_input();
204	tx_builder.add_mint_one_script_token_using_reference_script(
205		&governance_data.policy.script(),
206		&gov_tx_input,
207		&costs,
208	)?;
209
210	Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
211}
212
213fn update_d_param_tx(
214	validator: &PlutusScript,
215	policy: &PlutusScript,
216	d_parameter: &DParameter,
217	script_utxo: &OgmiosUtxo,
218	governance_data: &GovernanceData,
219	costs: Costs,
220	ctx: &TransactionContext,
221) -> anyhow::Result<Transaction> {
222	let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
223
224	let mut inputs = TxInputsBuilder::new();
225	inputs.add_script_utxo_input(
226		script_utxo,
227		validator,
228		&unit_plutus_data(),
229		&costs.get_one_spend(),
230	)?;
231	tx_builder.set_inputs(&inputs);
232
233	tx_builder.add_output_with_one_script_token(
234		validator,
235		policy,
236		&d_parameter_to_plutus_data(d_parameter),
237		ctx,
238	)?;
239
240	let gov_tx_input = governance_data.utxo_id_as_tx_input();
241	tx_builder.add_mint_one_script_token_using_reference_script(
242		&governance_data.policy.script(),
243		&gov_tx_input,
244		&costs,
245	)?;
246
247	Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
248}
249
250/// Returns D-parameter.
251pub async fn get_d_param<C: QueryLedgerState + QueryNetwork>(
252	genesis_utxo: UtxoId,
253	client: &C,
254) -> anyhow::Result<Option<DParameter>> {
255	let network = client.shelley_genesis_configuration().await?.network.to_csl();
256	let scripts = crate::scripts_data::d_parameter_scripts(genesis_utxo, network)?;
257	let validator_utxos = client.query_utxos(&[scripts.validator_address.clone()]).await?;
258	Ok(get_current_d_parameter(validator_utxos)?.map(|(_, d_parameter)| d_parameter))
259}