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