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	/// Unused, set to 0
37	pub t0: u64,
38	/// Reserve token asset Id
39	pub token: AssetId,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43/// Type representing reserve settings that *are* changeable after creation
44pub struct ReserveMutableSettings {
45	/// Asset name of the 'total accrued function' minting policy, also called V-function,
46	/// that determines the maximum amount of tokens that can be released from the reserve at any given moment.
47	/// This is accomplished by minting v-function tokens with the policy, and comparing the released amount
48	/// to the token amount in the reserver validator script.
49	pub total_accrued_function_asset_name: PolicyId,
50	/// Initial amount of tokens to deposit. They must be present in the payment wallet.
51	pub initial_incentive: u64,
52}
53
54#[derive(Debug, Clone, PartialEq)]
55/// Type representing continuously changing information about the reserve
56pub struct ReserveStats {
57	/// Total amount of tokens released from reserve
58	pub token_total_amount_transferred: u64,
59}
60
61impl From<ReserveRedeemer> for PlutusData {
62	fn from(value: ReserveRedeemer) -> Self {
63		PlutusData::new_empty_constr_plutus_data(&BigNum::from(value as u64))
64	}
65}
66
67impl From<ReserveDatum> for PlutusData {
68	fn from(value: ReserveDatum) -> Self {
69		VersionedGenericDatum {
70			datum: {
71				let mut immutable_settings = PlutusList::new();
72				let t0 = PlutusData::new_integer(&BigInt::zero());
73				immutable_settings.add(&t0);
74				let (policy_id_bytes, asset_name_bytes) = {
75					let AssetId { policy_id, asset_name } = value.immutable_settings.token.clone();
76					(policy_id.0.to_vec(), asset_name.0.to_vec())
77				};
78				let token_data: PlutusData = {
79					let mut asset_data = PlutusList::new();
80					asset_data.add(&PlutusData::new_bytes(policy_id_bytes));
81					asset_data.add(&PlutusData::new_bytes(asset_name_bytes));
82					PlutusData::new_constr_plutus_data(&ConstrPlutusData::new(
83						&BigNum::zero(),
84						&asset_data,
85					))
86				};
87				immutable_settings.add(&token_data);
88
89				let mut v_function_hash_and_initial_incentive = PlutusList::new();
90				v_function_hash_and_initial_incentive.add(&PlutusData::new_bytes(
91					value.mutable_settings.total_accrued_function_asset_name.0.to_vec(),
92				));
93				v_function_hash_and_initial_incentive.add(&PlutusData::new_integer(&BigInt::from(
94					value.mutable_settings.initial_incentive,
95				)));
96
97				let mut datum = PlutusList::new();
98				datum.add(&PlutusData::new_list(&immutable_settings));
99				datum.add(&PlutusData::new_list(&v_function_hash_and_initial_incentive));
100				datum.add(&PlutusData::new_integer(
101					&value.stats.token_total_amount_transferred.into(),
102				));
103				PlutusData::new_list(&datum)
104			},
105			// this empty constructor below is Plutus encoding of `()`
106			appendix: PlutusData::new_empty_constr_plutus_data(&BigNum::zero()),
107			version: 0,
108		}
109		.into()
110	}
111}
112
113impl TryFrom<PlutusData> for ReserveDatum {
114	type Error = DataDecodingError;
115
116	fn try_from(datum: PlutusData) -> DecodingResult<Self> {
117		Self::decode(&datum)
118	}
119}
120
121impl VersionedDatum for ReserveDatum {
122	const NAME: &str = "ReserveDatum";
123
124	fn decode(datum: &PlutusData) -> DecodingResult<Self> {
125		match plutus_data_version_and_payload(datum) {
126			Some(VersionedGenericDatum { version: 0, datum, .. }) => {
127				decode_v0_reserve_datum(&datum)
128					.ok_or_else(|| decoding_error_and_log(&datum, "ReserveDatum", "invalid data"))
129			},
130			_ => Err(decoding_error_and_log(datum, "ReserveDatum", "unversioned datum")),
131		}
132	}
133}
134
135impl ReserveDatum {
136	/// Calculates new reserve after withdrawal by adding `amount` to `token_total_amount_transferred`.
137	pub fn after_withdrawal(self, amount: u64) -> Self {
138		Self {
139			stats: ReserveStats {
140				token_total_amount_transferred: self.stats.token_total_amount_transferred + amount,
141			},
142			..self
143		}
144	}
145}
146
147fn decode_v0_reserve_datum(datum: &PlutusData) -> Option<ReserveDatum> {
148	let outer_list = datum.as_list()?;
149	let mut outer_iter = outer_list.into_iter();
150
151	let immutable_settings_list = outer_iter.next()?.as_list()?;
152	let mut immutable_settings_iter = immutable_settings_list.into_iter();
153	let t0: u64 = immutable_settings_iter.next()?.as_integer()?.as_u64()?.into();
154	let token = decode_token_id_datum(immutable_settings_iter.next()?)?;
155
156	let mutable_settings_list = outer_iter.next()?.as_list()?;
157	let mut mutable_settings_iter = mutable_settings_list.into_iter();
158	let total_accrued_function_script_hash =
159		PolicyId(mutable_settings_iter.next()?.as_bytes()?.to_vec().try_into().ok()?);
160	let initial_incentive = mutable_settings_iter.next()?.as_integer()?.as_u64()?.into();
161
162	let stats = ReserveStats {
163		token_total_amount_transferred: outer_iter.next()?.as_integer()?.as_u64()?.into(),
164	};
165
166	Some(ReserveDatum {
167		immutable_settings: ReserveImmutableSettings { t0, token },
168		mutable_settings: ReserveMutableSettings {
169			total_accrued_function_asset_name: total_accrued_function_script_hash,
170			initial_incentive,
171		},
172		stats,
173	})
174}
175
176fn decode_token_id_datum(pd: &PlutusData) -> Option<AssetId> {
177	let token_id_list = pd
178		.as_constr_plutus_data()
179		.filter(|constr| constr.alternative() == BigNum::zero())
180		.map(|constr| constr.data())?;
181	let mut token_id_list_iter = token_id_list.into_iter();
182	let policy_id = token_id_list_iter.next()?.as_bytes()?.to_vec();
183	let asset_name = token_id_list_iter.next()?.as_bytes()?.to_vec();
184	Some(AssetId {
185		policy_id: PolicyId(policy_id.try_into().ok()?),
186		asset_name: AssetName(asset_name.try_into().ok()?),
187	})
188}
189
190#[cfg(test)]
191mod tests {
192	use cardano_serialization_lib::PlutusData;
193	use pretty_assertions::assert_eq;
194	use sidechain_domain::{AssetName, PolicyId};
195
196	use crate::test_helpers::test_plutus_data;
197
198	use super::{ReserveDatum, ReserveImmutableSettings, ReserveMutableSettings, ReserveStats};
199
200	fn test_datum() -> ReserveDatum {
201		ReserveDatum {
202			immutable_settings: ReserveImmutableSettings {
203				t0: 0,
204				token: sidechain_domain::AssetId {
205					policy_id: PolicyId([0; 28]),
206					asset_name: AssetName::from_hex_unsafe("aabbcc"),
207				},
208			},
209			mutable_settings: ReserveMutableSettings {
210				total_accrued_function_asset_name: PolicyId([2; 28]),
211				initial_incentive: 0,
212			},
213			stats: ReserveStats { token_total_amount_transferred: 1000 },
214		}
215	}
216
217	fn test_datum_plutus_data() -> PlutusData {
218		test_plutus_data!({"list":[
219			{"list":[
220				{"list":[
221					{"int": 0},
222					{"constructor":0,
223					 "fields":[
224						{"bytes": "00000000000000000000000000000000000000000000000000000000"},
225						{"bytes": "aabbcc"}]}
226				]},
227				{"list":[
228					{"bytes": "02020202020202020202020202020202020202020202020202020202"},
229					{"int": 0}
230				]},
231				{"int": 1000}
232			]},
233			{"constructor":0,"fields":[]},
234			{"int":0}
235		]})
236	}
237
238	#[test]
239	fn encode() {
240		assert_eq!(PlutusData::from(test_datum()), test_datum_plutus_data())
241	}
242
243	#[test]
244	fn decode() {
245		assert_eq!(ReserveDatum::try_from(test_datum_plutus_data()).unwrap(), test_datum())
246	}
247}