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::{
20	await_tx::AwaitTx,
21	cardano_keys::CardanoPaymentSigningKey,
22	csl::{
23		Costs, MultiAssetExt, OgmiosUtxoExt, Script, TransactionBuilderExt, TransactionContext,
24		TransactionExt, TransactionOutputAmountBuilderExt, get_builder_config,
25	},
26	governance::GovernanceData,
27	multisig::{MultiSigSmartContractResult, submit_or_create_tx_to_sign},
28	scripts_data::ReserveScripts,
29};
30use cardano_serialization_lib::{
31	JsError, MultiAsset, Transaction, TransactionBuilder, TransactionOutput,
32	TransactionOutputBuilder,
33};
34use ogmios_client::{
35	query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
36	query_network::QueryNetwork,
37	transactions::Transactions,
38};
39use partner_chains_plutus_data::reserve::{
40	ReserveDatum, ReserveImmutableSettings, ReserveMutableSettings, ReserveStats,
41};
42use sidechain_domain::{AssetId, PolicyId, UtxoId};
43
44/// Creates new reserve with the given [ReserveParameters].
45pub async fn create_reserve_utxo<
46	T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
47	A: AwaitTx,
48>(
49	parameters: ReserveParameters,
50	genesis_utxo: UtxoId,
51	payment_key: &CardanoPaymentSigningKey,
52	client: &T,
53	await_tx: &A,
54) -> anyhow::Result<MultiSigSmartContractResult> {
55	let payment_ctx = TransactionContext::for_payment_key(payment_key, client).await?;
56	let governance = GovernanceData::get(genesis_utxo, client).await?;
57	let reserve = ReserveData::get(genesis_utxo, &payment_ctx, client).await?;
58
59	submit_or_create_tx_to_sign(
60		&governance,
61		payment_ctx,
62		|costs, ctx| create_reserve_tx(&parameters, &reserve, &governance, costs, &ctx),
63		"Create Reserve",
64		client,
65		await_tx,
66	)
67	.await
68}
69
70/// Parameters for token reserve.
71pub struct ReserveParameters {
72	/// [PolicyId] of the V-function minting policy.
73	pub total_accrued_function_script_hash: PolicyId,
74	/// [AssetId] of reserve token.
75	pub token: AssetId,
76	/// Initial deposit amount.
77	pub initial_deposit: u64,
78}
79
80impl From<&ReserveParameters> for ReserveDatum {
81	fn from(value: &ReserveParameters) -> Self {
82		ReserveDatum {
83			immutable_settings: ReserveImmutableSettings { token: value.token.clone() },
84			mutable_settings: ReserveMutableSettings {
85				total_accrued_function_asset_name: value.total_accrued_function_script_hash.clone(),
86				// this value is hard-coded to zero as a temporary fix because of a vulnerability in the on-chain
87				// contract code that would allow the reserve to be drained for non-zero values
88				initial_incentive: 0,
89			},
90			stats: ReserveStats { token_total_amount_transferred: 0 },
91		}
92	}
93}
94
95fn create_reserve_tx(
96	parameters: &ReserveParameters,
97	reserve: &ReserveData,
98	governance: &GovernanceData,
99	costs: Costs,
100	ctx: &TransactionContext,
101) -> anyhow::Result<Transaction> {
102	let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
103
104	tx_builder.add_mint_one_script_token_using_reference_script(
105		&Script::Plutus(reserve.scripts.auth_policy.clone()),
106		&reserve.auth_policy_version_utxo.to_csl_tx_input(),
107		&costs,
108	)?;
109	tx_builder.add_output(&reserve_validator_output(parameters, &reserve.scripts, ctx)?)?;
110
111	let gov_tx_input = governance.utxo_id_as_tx_input();
112	tx_builder.add_mint_one_script_token_using_reference_script(
113		&governance.policy.script(),
114		&gov_tx_input,
115		&costs,
116	)?;
117	tx_builder.add_script_reference_input(
118		&reserve.validator_version_utxo.to_csl_tx_input(),
119		reserve.scripts.validator.bytes.len(),
120	);
121	Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
122}
123
124// Creates output with reserve token and the initial deposit
125fn reserve_validator_output(
126	parameters: &ReserveParameters,
127	scripts: &ReserveScripts,
128	ctx: &TransactionContext,
129) -> Result<TransactionOutput, JsError> {
130	let amount_builder = TransactionOutputBuilder::new()
131		.with_address(&scripts.validator.address(ctx.network))
132		.with_plutus_data(&ReserveDatum::from(parameters).into())
133		.next()?;
134	let ma = MultiAsset::new()
135		.with_asset_amount(&scripts.auth_policy.empty_name_asset(), 1u64)?
136		.with_asset_amount(&parameters.token, parameters.initial_deposit)?;
137
138	amount_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()
139}