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