partner_chains_cardano_offchain/reserve/
create.rs

1//! Transaction that creates a new reserve.
2//!
3//! Specification:
4//! 1. The transaction should mint two tokens:
5//!   * 1 Reserve Auth Policy Token (using reference script)
6//!   * 1 Governance Policy Token (using reference script)
7//! 2. The transaction should have two outputs:
8//!   * Reserve Validator output that:
9//!   * * has Reward Tokens and minted Reserve Auth Policy Token
10//!   * * has Plutus Data (in our "versioned format"): `[[[<Encoded Token>], [Bytes(v_function_hash), Int(initial_incentive)], [Int(0)]], Constr(0, []), Int(0)]`,
11//!       where `<Encoded Token>` is `Constr(0, [Bytes(policy_id), Bytes(asset_name)])`.
12//!   * Change output that keeps the Governance Token and change of other tokens
13//! 3. The transaction should have three script reference inputs:
14//!   * Reserve Auth Version Utxo
15//!   * Reserve Validator Version Utxo
16//!   * Governance Policy Script
17
18use super::ReserveData;
19use crate::csl::unit_plutus_data;
20use crate::reserve::create::Script::Plutus;
21use crate::{
22	await_tx::AwaitTx,
23	cardano_keys::CardanoPaymentSigningKey,
24	csl::{
25		Costs, MultiAssetExt, OgmiosUtxoExt, Script, TransactionBuilderExt, TransactionContext,
26		TransactionExt, TransactionOutputAmountBuilderExt, get_builder_config,
27	},
28	governance::GovernanceData,
29	multisig::{MultiSigSmartContractResult, submit_or_create_tx_to_sign},
30	scripts_data::ReserveScripts,
31};
32use cardano_serialization_lib::{
33	Int, JsError, MultiAsset, Transaction, TransactionBuilder, TransactionOutput,
34	TransactionOutputBuilder,
35};
36use ogmios_client::{
37	query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
38	query_network::QueryNetwork,
39	transactions::Transactions,
40};
41use partner_chains_plutus_data::reserve::{
42	ReserveDatum, ReserveImmutableSettings, ReserveMutableSettings, ReserveStats,
43};
44use sidechain_domain::{AssetId, PolicyId, UtxoId};
45use std::num::NonZero;
46
47/// Creates new reserve with the given [ReserveParameters].
48pub async fn create_reserve_utxo<
49	T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
50	A: AwaitTx,
51>(
52	parameters: ReserveParameters,
53	genesis_utxo: UtxoId,
54	payment_key: &CardanoPaymentSigningKey,
55	client: &T,
56	await_tx: &A,
57) -> anyhow::Result<MultiSigSmartContractResult> {
58	let payment_ctx = TransactionContext::for_payment_key(payment_key, client).await?;
59	let governance = GovernanceData::get(genesis_utxo, client).await?;
60	let reserve = ReserveData::get(genesis_utxo, &payment_ctx, client).await?;
61
62	submit_or_create_tx_to_sign(
63		&governance,
64		payment_ctx,
65		|costs, ctx| create_reserve_tx(&parameters, &reserve, &governance, costs, &ctx),
66		"Create Reserve",
67		client,
68		await_tx,
69	)
70	.await
71}
72
73/// Parameters for token reserve.
74pub struct ReserveParameters {
75	/// [PolicyId] of the V-function minting policy.
76	pub total_accrued_function_script_hash: PolicyId,
77	/// [AssetId] of reserve token.
78	pub token: AssetId,
79	/// Initial deposit amount.
80	pub initial_deposit: u64,
81	/// Amount of illiquid circulation supply authority tokens to mint.
82	pub ics_initial_utxos_amount: NonZero<u64>,
83}
84
85impl From<&ReserveParameters> for ReserveDatum {
86	fn from(value: &ReserveParameters) -> Self {
87		ReserveDatum {
88			immutable_settings: ReserveImmutableSettings { token: value.token.clone() },
89			mutable_settings: ReserveMutableSettings {
90				total_accrued_function_asset_name: value.total_accrued_function_script_hash.clone(),
91				// this value is hard-coded to zero as a temporary fix because of a vulnerability in the on-chain
92				// contract code that would allow the reserve to be drained for non-zero values
93				initial_incentive: 0,
94			},
95			stats: ReserveStats { token_total_amount_transferred: 0 },
96		}
97	}
98}
99
100fn create_reserve_tx(
101	parameters: &ReserveParameters,
102	reserve: &ReserveData,
103	governance: &GovernanceData,
104	costs: Costs,
105	ctx: &TransactionContext,
106) -> anyhow::Result<Transaction> {
107	let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
108
109	tx_builder.add_mint_one_script_token_using_reference_script(
110		&Script::Plutus(reserve.scripts.auth_policy.clone()),
111		&reserve.auth_policy_version_utxo.to_csl_tx_input(),
112		&costs,
113	)?;
114	tx_builder.add_output(&reserve_validator_output(parameters, &reserve.scripts, ctx)?)?;
115
116	let gov_tx_input = governance.utxo_id_as_tx_input();
117	tx_builder.add_mint_one_script_token_using_reference_script(
118		&governance.policy.script(),
119		&gov_tx_input,
120		&costs,
121	)?;
122	tx_builder.add_script_reference_input(
123		&reserve.validator_version_utxo.to_csl_tx_input(),
124		reserve.scripts.validator.bytes.len(),
125	);
126
127	tx_builder.add_mint_script_token_using_reference_script(
128		&Plutus(reserve.scripts.illiquid_circulation_supply_auth_token_policy.clone()),
129		&reserve
130			.illiquid_circulation_supply_authority_token_policy_version_utxo
131			.to_csl_tx_input(),
132		&Int::new(&parameters.ics_initial_utxos_amount.get().into()),
133		&costs,
134	)?;
135	// Create ICS Authorized Outputs. These contain special ICS Authority Token,
136	// that prevents UTxOs from being merged all into one.
137	for _ in 0..parameters.ics_initial_utxos_amount.into() {
138		tx_builder.add_output(&ics_validator_output(&reserve.scripts, ctx)?)?;
139	}
140
141	let tx = tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses();
142
143	Ok(tx)
144}
145
146// Creates output with reserve token and the initial deposit
147fn reserve_validator_output(
148	parameters: &ReserveParameters,
149	scripts: &ReserveScripts,
150	ctx: &TransactionContext,
151) -> Result<TransactionOutput, JsError> {
152	let amount_builder = TransactionOutputBuilder::new()
153		.with_address(&scripts.validator.address(ctx.network))
154		.with_plutus_data(&ReserveDatum::from(parameters).into())
155		.next()?;
156	let ma = MultiAsset::new()
157		.with_asset_amount(&scripts.auth_policy.empty_name_asset(), 1u64)?
158		.with_asset_amount(&parameters.token, parameters.initial_deposit)?;
159
160	amount_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()
161}
162
163fn ics_validator_output(
164	scripts: &ReserveScripts,
165	ctx: &TransactionContext,
166) -> Result<TransactionOutput, JsError> {
167	let amount_builder = TransactionOutputBuilder::new()
168		.with_address(&scripts.illiquid_circulation_supply_validator.address(ctx.network))
169		.with_plutus_data(&unit_plutus_data())
170		.next()?;
171	let ma = MultiAsset::new().with_asset_amount(
172		&scripts.illiquid_circulation_supply_auth_token_policy.empty_name_asset(),
173		1u64,
174	)?;
175
176	amount_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()
177}