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"): `[[[Int(t0), <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			// `t0` field is not used by on-chain code of partner-chains smart-contracts,
84			// but only gave a possibility for user to store "t0" for his own V-function.
85			// Not configurable anymore, hardcoded to 0. If users need "t0" for their V-function, they are responsible for storing it somewhere.
86			immutable_settings: ReserveImmutableSettings { t0: 0, token: value.token.clone() },
87			mutable_settings: ReserveMutableSettings {
88				total_accrued_function_asset_name: value.total_accrued_function_script_hash.clone(),
89				// this value is hard-coded to zero as a temporary fix because of a vulnerability in the on-chain
90				// contract code that would allow the reserve to be drained for non-zero values
91				initial_incentive: 0,
92			},
93			stats: ReserveStats { token_total_amount_transferred: 0 },
94		}
95	}
96}
97
98fn create_reserve_tx(
99	parameters: &ReserveParameters,
100	reserve: &ReserveData,
101	governance: &GovernanceData,
102	costs: Costs,
103	ctx: &TransactionContext,
104) -> anyhow::Result<Transaction> {
105	let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);
106
107	tx_builder.add_mint_one_script_token_using_reference_script(
108		&Script::Plutus(reserve.scripts.auth_policy.clone()),
109		&reserve.auth_policy_version_utxo.to_csl_tx_input(),
110		&costs,
111	)?;
112	tx_builder.add_output(&reserve_validator_output(parameters, &reserve.scripts, ctx)?)?;
113
114	let gov_tx_input = governance.utxo_id_as_tx_input();
115	tx_builder.add_mint_one_script_token_using_reference_script(
116		&governance.policy.script(),
117		&gov_tx_input,
118		&costs,
119	)?;
120	tx_builder.add_script_reference_input(
121		&reserve.validator_version_utxo.to_csl_tx_input(),
122		reserve.scripts.validator.bytes.len(),
123	);
124	Ok(tx_builder.balance_update_and_build(ctx)?.remove_native_script_witnesses())
125}
126
127// Creates output with reserve token and the initial deposit
128fn reserve_validator_output(
129	parameters: &ReserveParameters,
130	scripts: &ReserveScripts,
131	ctx: &TransactionContext,
132) -> Result<TransactionOutput, JsError> {
133	let amount_builder = TransactionOutputBuilder::new()
134		.with_address(&scripts.validator.address(ctx.network))
135		.with_plutus_data(&ReserveDatum::from(parameters).into())
136		.next()?;
137	let ma = MultiAsset::new()
138		.with_asset_amount(&scripts.auth_policy.empty_name_asset(), 1u64)?
139		.with_asset_amount(&parameters.token, parameters.initial_deposit)?;
140
141	amount_builder.with_minimum_ada_and_asset(&ma, ctx)?.build()
142}