cardano_sdk/cardano/output.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5use crate::{
6 Address, Datum, Hash, PlutusData, PlutusScript, Value, address::kind::*, cbor, cbor::ToCbor,
7 pallas, pretty,
8};
9use anyhow::anyhow;
10use std::{fmt, sync::Arc};
11
12pub mod change_strategy;
13
14/// Technically, this is a protocol parameter. It is however usually the same on all networks, and
15/// hasn't changed in many years. If it ever change, we can always adjust the library to the
16/// maximum of the two values. It is much more convenient than carrying protocol parameters around.
17const MIN_VALUE_PER_UTXO_BYTE: u64 = 4310;
18
19/// The CBOR overhead accounting for the in-memory size of inputs and utxo, as per [CIP-0055](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0055#the-new-minimum-lovelace-calculation).
20const MIN_LOVELACE_VALUE_CBOR_OVERHEAD: u64 = 160;
21
22/// A transaction output, which comprises of at least an [`Address`] and a [`Value<u64>`].
23///
24/// The value can be either explicit set using [`Self::new`] or defined to the minimum acceptable
25/// by the protocol using [`Self::to`].
26///
27/// Optionally, one can attach an [`Datum`] and/or a [`PlutusScript`] via
28/// [`Self::with_datum`]/[`Self::with_datum_hash`] and [`Self::with_plutus_script`] respectively.
29///
30/// <div class="warning">Native scripts as reference scripts aren't yet supported. Only Plutus
31/// scripts are.</div>
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct Output {
34 address: Address<Any>,
35 value: DeferredValue,
36 datum: Option<Arc<Datum>>,
37 script: Option<Arc<PlutusScript>>,
38}
39
40impl fmt::Display for Output {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(
43 f,
44 "{:#?}",
45 pretty::Fmt(|f: &mut fmt::Formatter<'_>| {
46 let mut debug_struct = f.debug_struct("Output");
47
48 debug_struct.field("address", &pretty::ViaDisplayNoAlloc(self.address()));
49
50 debug_struct.field("value", &pretty::ViaDisplayNoAlloc(self.value()));
51
52 if let Some(datum) = self.datum() {
53 debug_struct.field("datum", &pretty::ViaDisplayNoAlloc(datum));
54 }
55
56 if let Some(script) = self.script() {
57 debug_struct.field("script", &pretty::ViaDisplayNoAlloc(script));
58 }
59
60 debug_struct.finish()
61 })
62 )
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67enum DeferredValue {
68 Minimum(Arc<Value<u64>>),
69 Explicit(Arc<Value<u64>>),
70}
71
72// -------------------------------------------------------------------- Building
73
74impl Output {
75 /// Construct a new output from an [`Address`] and a [`Value<u64>`]. See also [`Self::to`] for
76 /// constructing a value without an explicit value.
77 pub fn new(address: Address<Any>, value: Value<u64>) -> Self {
78 Self {
79 address,
80 value: DeferredValue::Explicit(Arc::new(value)),
81 datum: None,
82 script: None,
83 }
84 }
85
86 /// Like [`Self::new`], but assumes a minimum lovelace value as output. The value automatically
87 /// adjusts based on the other Output's elements (assets, scripts, etc..).
88 pub fn to(address: Address<Any>) -> Self {
89 let mut output = Self {
90 address,
91 value: DeferredValue::Minimum(Arc::new(Value::default())),
92 datum: None,
93 script: None,
94 };
95
96 output.set_minimum_utxo_value();
97
98 output
99 }
100
101 /// Attach assets to the output, while preserving the lovelace value.
102 pub fn with_assets<AssetName>(
103 mut self,
104 assets: impl IntoIterator<Item = (Hash<28>, impl IntoIterator<Item = (AssetName, u64)>)>,
105 ) -> Self
106 where
107 AssetName: AsRef<[u8]>,
108 {
109 self.value = DeferredValue::Minimum(Arc::new(Value::default().with_assets(assets)));
110 self.set_minimum_utxo_value();
111 self
112 }
113
114 /// Attach a reference script to the output.
115 pub fn with_plutus_script(mut self, plutus_script: PlutusScript) -> Self {
116 self.script = Some(Arc::new(plutus_script));
117 self.set_minimum_utxo_value();
118 self
119 }
120
121 /// Attach a datum reference as [`struct@Hash<32>`] to the output.
122 pub fn with_datum_hash(mut self, hash: Hash<32>) -> Self {
123 self.datum = Some(Arc::new(Datum::Hash(hash)));
124 self.set_minimum_utxo_value();
125 self
126 }
127
128 /// Attach a plain [`PlutusData`] datum to the output.
129 pub fn with_datum(mut self, data: PlutusData<'static>) -> Self {
130 self.datum = Some(Arc::new(Datum::Inline(data)));
131 self.set_minimum_utxo_value();
132 self
133 }
134
135 /// Adjust the lovelace quantities of deferred values using the size of the serialised output.
136 /// This does nothing on explicitly given values -- EVEN WHEN they are below the minimum
137 /// threshold.
138 fn set_minimum_utxo_value(&mut self) {
139 // Only compute the minimum when it's actually required. Note that we cannot do this within
140 // the next block because of the immutable borrow that occurs already.
141 let min_acceptable_value = match &self.value {
142 DeferredValue::Explicit(_) => 0,
143 DeferredValue::Minimum(_) => self.min_acceptable_value(),
144 };
145
146 if let DeferredValue::Minimum(rc) = &mut self.value {
147 let value: &mut Value<u64> = Arc::make_mut(rc);
148 value.with_lovelace(min_acceptable_value);
149 }
150 }
151}
152
153// ------------------------------------------------------------------ Inspecting
154
155impl Output {
156 pub fn address(&self) -> &Address<Any> {
157 &self.address
158 }
159
160 pub fn value(&self) -> &Value<u64> {
161 match &self.value {
162 DeferredValue::Minimum(value) | DeferredValue::Explicit(value) => value.as_ref(),
163 }
164 }
165
166 pub fn script(&self) -> Option<&PlutusScript> {
167 self.script.as_deref()
168 }
169
170 pub fn datum(&self) -> Option<&Datum> {
171 self.datum.as_deref()
172 }
173
174 /// The minimum quantity of lovelace acceptable to carry this output. Address' delegation,
175 /// assets, scripts and datums may increase this value.
176 ///
177 /// # examples
178 ///
179 /// ```rust
180 /// # use cardano_sdk::*;
181 /// // Simple, undelegated address. About as low as we can go.
182 /// assert_eq!(
183 /// output!("addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h")
184 /// .min_acceptable_value(),
185 /// 857690,
186 /// );
187 ///
188 /// // Address with delegation.
189 /// assert_eq!(
190 /// output!("addr1qytp6yfl9wwamcqu3j5kqhjz8hlgkt62nd82d837g9dlsmn85wjc8sjtq2wqxfmahmpn6h85y0ug7mzclf2jl4zyt3vq587s69")
191 /// .min_acceptable_value(),
192 /// 978370,
193 /// );
194 ///
195 /// // Undelegated address with some native assets.
196 /// assert_eq!(
197 /// output!("addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h")
198 /// .with_assets([
199 /// (
200 /// hash!("279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f"),
201 /// [(b"SNEK".to_vec(), 1_000_000_000)]
202 /// )
203 /// ])
204 /// .min_acceptable_value(),
205 /// 1043020,
206 /// );
207 ///
208 /// // Undelegated address with some inline datum.
209 /// assert_eq!(
210 /// output!("addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h")
211 /// .with_datum(PlutusData::list([
212 /// PlutusData::integer(14),
213 /// PlutusData::integer(42),
214 /// PlutusData::bytes(b"foobar"),
215 /// ]))
216 /// .min_acceptable_value(),
217 /// 935270,
218 /// );
219 ///
220 /// // Undelegated address with some datum hash.
221 /// assert_eq!(
222 /// output!("addr1v83gkkw3nqzakg5xynlurqcfqhgd65vkfvf5xv8tx25ufds2yvy2h")
223 /// .with_datum_hash(hash!("279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f00000000"))
224 /// .min_acceptable_value(),
225 /// 1017160,
226 /// );
227 /// ```
228 pub fn min_acceptable_value(&self) -> u64 {
229 // In case where values are too small, we still count for 5 bytes to avoid having to search
230 // for a fixed point. This is because CBOR uses variable-length encoding for integers,
231 // according to the following rules:
232 //
233 // | value | encoding size |
234 // | ---------------------- | ------------------- |
235 // | 0 <= n < 24 | 1 byte |
236 // | 24 <= n < 2 ^ 8 | 2 bytes |
237 // | 2 ^ 8 <= n < 2 ^ 16 | 3 bytes |
238 // | 2 ^ 16 <= n < 2 ^ 32 | 5 bytes |
239 // | 2 ^ 32 <= n < 2 ^ 64 | 9 bytes |
240 //
241 // Values are at least MIN_VALUE_PER_UTXO_BYTE * MIN_LOVELACE_VALUE_CBOR_OVERHEAD = 689600,
242 // so that means the encoding will never be smaller than 5 bytes; if it is, we must inflate
243 // the size artificially to compensate.
244 let current_value = self.value().lovelace();
245
246 let extra_size = match current_value {
247 _ if current_value < 24 => 4,
248 _ if current_value < 256 => 3,
249 _ if current_value < 65535 => 2,
250 _ => 0,
251 };
252
253 MIN_VALUE_PER_UTXO_BYTE * (self.size() + MIN_LOVELACE_VALUE_CBOR_OVERHEAD + extra_size)
254 }
255
256 fn size(&self) -> u64 {
257 self.to_cbor().len() as u64
258 }
259}
260
261// ------------------------------------------------------------ Converting (from)
262
263impl TryFrom<pallas::TransactionOutput> for Output {
264 type Error = anyhow::Error;
265
266 fn try_from(source: pallas::TransactionOutput) -> anyhow::Result<Self> {
267 let (address, value, datum_opt, plutus_script_opt) = match source {
268 pallas::TransactionOutput::Legacy(legacy) => {
269 let address = Address::try_from(legacy.address.as_slice())?;
270 let value = Value::from(&legacy.amount);
271 let datum_opt = legacy.datum_hash.map(|hash| Datum::Hash(Hash::from(hash)));
272 let plutus_script_opt = None;
273
274 Ok::<_, anyhow::Error>((address, value, datum_opt, plutus_script_opt))
275 }
276
277 pallas::TransactionOutput::PostAlonzo(modern) => {
278 let address = Address::try_from(modern.address.as_slice())?;
279 let value = Value::from(&modern.value);
280 let datum_opt = match modern.datum_option {
281 None => None,
282 Some(pallas::DatumOption::Hash(hash)) => Some(Datum::Hash(Hash::from(hash))),
283 Some(pallas::DatumOption::Data(data)) => {
284 Some(Datum::Inline(PlutusData::from(data.0)))
285 }
286 };
287 let plutus_script_opt = match modern.script_ref.map(|wrap| wrap.unwrap()) {
288 None => Ok(None),
289 Some(pallas::ScriptRef::NativeScript(_)) => {
290 Err(anyhow!("found unsupported native script at output"))
291 }
292 Some(pallas::ScriptRef::PlutusV1Script(script)) => {
293 Ok(Some(PlutusScript::from(script)))
294 }
295 Some(pallas::ScriptRef::PlutusV2Script(script)) => {
296 Ok(Some(PlutusScript::from(script)))
297 }
298 Some(pallas::ScriptRef::PlutusV3Script(script)) => {
299 Ok(Some(PlutusScript::from(script)))
300 }
301 }?;
302
303 Ok::<_, anyhow::Error>((address, value, datum_opt, plutus_script_opt))
304 }
305 }?;
306
307 let mut output = Output::new(address, value);
308
309 output = match datum_opt {
310 Some(Datum::Inline(data)) => output.with_datum(data),
311 Some(Datum::Hash(hash)) => output.with_datum_hash(hash),
312 None => output,
313 };
314
315 if let Some(plutus_script) = plutus_script_opt {
316 output = output.with_plutus_script(plutus_script);
317 }
318
319 Ok(output)
320 }
321}
322
323// -------------------------------------------------------------- Converting (to)
324
325impl From<&Output> for pallas::TransactionOutput {
326 fn from(output: &Output) -> Self {
327 pallas::TransactionOutput::PostAlonzo(pallas::PostAlonzoTransactionOutput {
328 address: pallas::Bytes::from(<Vec<u8>>::from(output.address())),
329 value: pallas::Value::from(output.value()),
330 datum_option: output.datum().map(|datum| match datum {
331 Datum::Hash(hash) => pallas::DatumOption::Hash(pallas::DatumHash::from(hash)),
332 Datum::Inline(data) => pallas::DatumOption::Data(pallas::CborWrap(
333 pallas::PlutusData::from(data.clone()),
334 )),
335 }),
336 script_ref: output
337 .script()
338 .map(|script| pallas::CborWrap(pallas::ScriptRef::from(script.clone()))),
339 })
340 }
341}
342
343impl From<Output> for pallas::TransactionOutput {
344 fn from(this: Output) -> Self {
345 pallas::TransactionOutput::from(&this)
346 }
347}
348
349// -------------------------------------------------------------------- Encoding
350
351impl<C> cbor::Encode<C> for Output {
352 fn encode<W: cbor::encode::write::Write>(
353 &self,
354 e: &mut cbor::Encoder<W>,
355 ctx: &mut C,
356 ) -> Result<(), cbor::encode::Error<W::Error>> {
357 pallas::TransactionOutput::from(self).encode(e, ctx)
358 }
359}
360
361impl<'d, C> cbor::Decode<'d, C> for Output {
362 fn decode(d: &mut cbor::Decoder<'d>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
363 let output: pallas::TransactionOutput = d.decode_with(ctx)?;
364 Self::try_from(output).map_err(cbor::decode::Error::message)
365 }
366}
367
368// ------------------------------------------------------------------------ WASM