partner_chains_plutus_data/
reserve.rs

1//! Plutus data types for reserve functionality.
2use crate::{
3	DataDecodingError, DecodingResult, VersionedDatum, VersionedGenericDatum,
4	decoding_error_and_log, plutus_data_version_and_payload,
5};
6use cardano_serialization_lib::{BigInt, BigNum, ConstrPlutusData, PlutusData, PlutusList};
7use sidechain_domain::{AssetId, AssetName, PolicyId};
8
9#[derive(Debug, Clone)]
10/// Redeemer for reserve
11pub enum ReserveRedeemer {
12	/// Deposit tokens to the reserve
13	DepositToReserve = 0,
14	/// Releases funds from the reserve to the illiquid supply
15	ReleaseFromReserve = 1,
16	/// Update mutable reserve management system settings
17	UpdateReserve = 2,
18	/// Releases all the remaining funds from the reserve to the illiquid supply
19	Handover = 3,
20}
21
22#[derive(Debug, Clone, PartialEq)]
23/// Datum of the reserve storing settings and stats
24pub struct ReserveDatum {
25	/// Reserve settings that *are not* changeable after creation
26	pub immutable_settings: ReserveImmutableSettings,
27	/// Reserve settings that *are* changeable after creation
28	pub mutable_settings: ReserveMutableSettings,
29	/// Reserve stats
30	pub stats: ReserveStats,
31}
32
33#[derive(Debug, Clone, PartialEq)]
34/// Type representing reserve settings that *are not* changeable after creation
35pub struct ReserveImmutableSettings {
36	/// Reserve token asset Id
37	pub token: AssetId,
38}
39
40#[derive(Debug, Clone, PartialEq)]
41/// Type representing reserve settings that *are* changeable after creation
42pub struct ReserveMutableSettings {
43	/// Asset name of the 'total accrued function' minting policy, also called V-function,
44	/// that determines the maximum amount of tokens that can be released from the reserve at any given moment.
45	/// This is accomplished by minting v-function tokens with the policy, and comparing the released amount
46	/// to the token amount in the reserver validator script.
47	pub total_accrued_function_asset_name: PolicyId,
48	/// Initial amount of tokens to deposit. They must be present in the payment wallet.
49	pub initial_incentive: u64,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53/// Type representing continuously changing information about the reserve
54pub struct ReserveStats {
55	/// Total amount of tokens released from reserve
56	pub token_total_amount_transferred: u64,
57}
58
59impl From<ReserveRedeemer> for PlutusData {
60	fn from(value: ReserveRedeemer) -> Self {
61		PlutusData::new_empty_constr_plutus_data(&BigNum::from(value as u64))
62	}
63}
64
65impl From<ReserveDatum> for PlutusData {
66	fn from(value: ReserveDatum) -> Self {
67		VersionedGenericDatum {
68			datum: {
69				let (policy_id_bytes, asset_name_bytes) = {
70					let AssetId { policy_id, asset_name } = value.immutable_settings.token.clone();
71					(policy_id.0.to_vec(), asset_name.0.to_vec())
72				};
73				let token_data: PlutusData = {
74					let mut asset_data = PlutusList::new();
75					asset_data.add(&PlutusData::new_bytes(policy_id_bytes));
76					asset_data.add(&PlutusData::new_bytes(asset_name_bytes));
77					PlutusData::new_constr_plutus_data(&ConstrPlutusData::new(
78						&BigNum::zero(),
79						&asset_data,
80					))
81				};
82
83				let mut v_function_hash_and_initial_incentive = PlutusList::new();
84				v_function_hash_and_initial_incentive.add(&PlutusData::new_bytes(
85					value.mutable_settings.total_accrued_function_asset_name.0.to_vec(),
86				));
87				v_function_hash_and_initial_incentive.add(&PlutusData::new_integer(&BigInt::from(
88					value.mutable_settings.initial_incentive,
89				)));
90
91				let mut datum = PlutusList::new();
92				datum.add(&token_data);
93				datum.add(&PlutusData::new_list(&v_function_hash_and_initial_incentive));
94				datum.add(&PlutusData::new_integer(
95					&value.stats.token_total_amount_transferred.into(),
96				));
97				PlutusData::new_list(&datum)
98			},
99			// this empty constructor below is Plutus encoding of `()`
100			appendix: PlutusData::new_empty_constr_plutus_data(&BigNum::zero()),
101			version: 0,
102		}
103		.into()
104	}
105}
106
107impl TryFrom<PlutusData> for ReserveDatum {
108	type Error = DataDecodingError;
109
110	fn try_from(datum: PlutusData) -> DecodingResult<Self> {
111		Self::decode(&datum)
112	}
113}
114
115impl VersionedDatum for ReserveDatum {
116	fn decode(datum: &PlutusData) -> DecodingResult<Self> {
117		match plutus_data_version_and_payload(datum) {
118			Some(VersionedGenericDatum { version: 0, datum, .. }) => {
119				decode_v0_reserve_datum(&datum)
120					.ok_or_else(|| decoding_error_and_log(&datum, "ReserveDatum", "invalid data"))
121			},
122			_ => Err(decoding_error_and_log(datum, "ReserveDatum", "unversioned datum")),
123		}
124	}
125}
126
127impl ReserveDatum {
128	/// Calculates new reserve after withdrawal by adding `amount` to `token_total_amount_transferred`.
129	pub fn after_withdrawal(self, amount: u64) -> Self {
130		Self {
131			stats: ReserveStats {
132				token_total_amount_transferred: self.stats.token_total_amount_transferred + amount,
133			},
134			..self
135		}
136	}
137}
138
139fn decode_v0_reserve_datum(datum: &PlutusData) -> Option<ReserveDatum> {
140	let outer_list = datum.as_list()?;
141	let mut outer_iter = outer_list.into_iter();
142
143	let token = decode_token_id_datum(outer_iter.next()?)?;
144
145	let mutable_settings_list = outer_iter.next()?.as_list()?;
146	let mut mutable_settings_iter = mutable_settings_list.into_iter();
147	let total_accrued_function_script_hash =
148		PolicyId(mutable_settings_iter.next()?.as_bytes()?.to_vec().try_into().ok()?);
149	let initial_incentive = mutable_settings_iter.next()?.as_integer()?.as_u64()?.into();
150
151	let stats = ReserveStats {
152		token_total_amount_transferred: outer_iter.next()?.as_integer()?.as_u64()?.into(),
153	};
154
155	Some(ReserveDatum {
156		immutable_settings: ReserveImmutableSettings { token },
157		mutable_settings: ReserveMutableSettings {
158			total_accrued_function_asset_name: total_accrued_function_script_hash,
159			initial_incentive,
160		},
161		stats,
162	})
163}
164
165fn decode_token_id_datum(pd: &PlutusData) -> Option<AssetId> {
166	let token_id_list = pd
167		.as_constr_plutus_data()
168		.filter(|constr| constr.alternative() == BigNum::zero())
169		.map(|constr| constr.data())?;
170	let mut token_id_list_iter = token_id_list.into_iter();
171	let policy_id = token_id_list_iter.next()?.as_bytes()?.to_vec();
172	let asset_name = token_id_list_iter.next()?.as_bytes()?.to_vec();
173	Some(AssetId {
174		policy_id: PolicyId(policy_id.try_into().ok()?),
175		asset_name: AssetName(asset_name.try_into().ok()?),
176	})
177}
178
179#[derive(Debug, Clone)]
180/// Redeemer for illiquid circulation supply
181pub enum IlliquidCirculationSupplyRedeemer {
182	/// Deposit tokens to the supply
183	DepositMoreToSupply = 0,
184	/// Withdraw from the illiquid supply
185	WithdrawFromSupply = 1,
186}
187
188impl From<IlliquidCirculationSupplyRedeemer> for PlutusData {
189	fn from(value: IlliquidCirculationSupplyRedeemer) -> Self {
190		PlutusData::new_integer(&BigInt::from(value as u64))
191	}
192}
193
194#[derive(Debug, Clone, PartialEq)]
195/// Datum of the illiquid circulation supply.
196pub struct IlliquidCirculationSupplyDatum;
197
198impl From<IlliquidCirculationSupplyDatum> for PlutusData {
199	fn from(_value: IlliquidCirculationSupplyDatum) -> Self {
200		VersionedGenericDatum {
201			datum: PlutusData::new_empty_constr_plutus_data(
202				&cardano_serialization_lib::BigNum::zero(),
203			),
204			appendix: PlutusData::new_empty_constr_plutus_data(
205				&cardano_serialization_lib::BigNum::zero(),
206			),
207			version: 0,
208		}
209		.into()
210	}
211}
212
213#[cfg(test)]
214mod tests {
215	use cardano_serialization_lib::PlutusData;
216	use pretty_assertions::assert_eq;
217	use sidechain_domain::{AssetName, PolicyId};
218
219	use crate::{reserve::IlliquidCirculationSupplyDatum, test_helpers::test_plutus_data};
220
221	use super::{ReserveDatum, ReserveImmutableSettings, ReserveMutableSettings, ReserveStats};
222
223	fn test_reserve_datum() -> ReserveDatum {
224		ReserveDatum {
225			immutable_settings: ReserveImmutableSettings {
226				token: sidechain_domain::AssetId {
227					policy_id: PolicyId([0; 28]),
228					asset_name: AssetName::from_hex_unsafe("aabbcc"),
229				},
230			},
231			mutable_settings: ReserveMutableSettings {
232				total_accrued_function_asset_name: PolicyId([2; 28]),
233				initial_incentive: 0,
234			},
235			stats: ReserveStats { token_total_amount_transferred: 1000 },
236		}
237	}
238
239	fn test_reserve_datum_plutus_data() -> PlutusData {
240		test_plutus_data!({"list":[
241			{"list":[
242					{"constructor":0,
243					 "fields":[
244						{"bytes": "00000000000000000000000000000000000000000000000000000000"},
245						{"bytes": "aabbcc"}]}
246				,
247				{"list":[
248					{"bytes": "02020202020202020202020202020202020202020202020202020202"},
249					{"int": 0}
250				]},
251				{"int": 1000}
252			]},
253			{"constructor":0,"fields":[]},
254			{"int":0}
255		]})
256	}
257
258	#[test]
259	fn encode_reserve_datum() {
260		assert_eq!(PlutusData::from(test_reserve_datum()), test_reserve_datum_plutus_data())
261	}
262
263	#[test]
264	fn decode_reserve_datum() {
265		assert_eq!(
266			ReserveDatum::try_from(test_reserve_datum_plutus_data()).unwrap(),
267			test_reserve_datum()
268		)
269	}
270
271	#[test]
272	fn encode_ics_datum() {
273		assert_eq!(
274			PlutusData::from(IlliquidCirculationSupplyDatum),
275			test_plutus_data!({"list":[
276				{"constructor":0,"fields":[]},
277				{"constructor":0,"fields":[]},
278				{"int":0}
279			]})
280		)
281	}
282}