Skip to main content

amaru_kernel/cardano/memoized/
script.rs

1// Copyright 2025 PRAGMA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use pallas_codec::utils::CborWrap;
16use pallas_primitives::{
17    KeepRaw,
18    conway::{NativeScript, PseudoScript},
19};
20use serde::ser::SerializeStruct;
21
22use crate::{
23    Bytes, MemoizedNativeScript, PlutusScript, cbor,
24    cbor::{bytes::ByteSlice, data::IanaTag},
25};
26
27pub type MemoizedScript = PseudoScript<MemoizedNativeScript>;
28
29pub fn serialize_memoized_script<S: serde::ser::Serializer>(
30    script: &MemoizedScript,
31    serializer: S,
32) -> Result<S::Ok, S::Error> {
33    let mut s = serializer.serialize_struct("MemoizedScript", 1)?;
34    match script {
35        // TODO: Adopt a less Rust-tainted encoding one day. Not doing it now because will remand
36        // re-generating and re-encoding all the ledger test vectors which is only tangential to
37        // the problem I am trying to solve.
38        MemoizedScript::NativeScript(native) => {
39            s.serialize_field("NativeScript", &hex::encode(native.original_bytes()))?;
40        }
41        MemoizedScript::PlutusV1Script(plutus) => {
42            s.serialize_field("PlutusV1Script", &hex::encode(plutus.as_ref()))?;
43        }
44        MemoizedScript::PlutusV2Script(plutus) => {
45            s.serialize_field("PlutusV2Script", &hex::encode(plutus.as_ref()))?;
46        }
47        MemoizedScript::PlutusV3Script(plutus) => {
48            s.serialize_field("PlutusV3Script", &hex::encode(plutus.as_ref()))?;
49        }
50    }
51    s.end()
52}
53
54pub fn script_original_bytes(script: &MemoizedScript) -> &[u8] {
55    match script {
56        MemoizedScript::NativeScript(native) => native.original_bytes(),
57        MemoizedScript::PlutusV1Script(plutus) => plutus.as_ref(),
58        MemoizedScript::PlutusV2Script(plutus) => plutus.as_ref(),
59        MemoizedScript::PlutusV3Script(plutus) => plutus.as_ref(),
60    }
61}
62
63pub fn decode_script<C>(d: &mut cbor::Decoder<'_>, ctx: &mut C) -> Result<MemoizedScript, cbor::decode::Error> {
64    let tag = d.tag()?;
65    if tag != IanaTag::Cbor.tag() {
66        return Err(cbor::decode::Error::message(format!(
67            "unexpected tag for script: expected {}, got {}",
68            IanaTag::Cbor.tag(),
69            tag
70        )));
71    }
72
73    let script: PseudoScript<MemoizedNativeScript> = cbor::Decoder::new(d.bytes()?)
74        .decode_with(ctx)
75        .map_err(|e| cbor::decode::Error::message(format!("failed to decode script: {e}")))?;
76
77    Ok(match script {
78        PseudoScript::NativeScript(n) => MemoizedScript::NativeScript(n),
79        PseudoScript::PlutusV1Script(s) => MemoizedScript::PlutusV1Script(s),
80        PseudoScript::PlutusV2Script(s) => MemoizedScript::PlutusV2Script(s),
81        PseudoScript::PlutusV3Script(s) => MemoizedScript::PlutusV3Script(s),
82    })
83}
84
85pub fn encode_script<W: cbor::encode::Write>(
86    script: &MemoizedScript,
87    e: &mut cbor::Encoder<W>,
88) -> Result<(), cbor::encode::Error<W::Error>> {
89    e.tag(IanaTag::Cbor)?;
90
91    let buffer = match script {
92        MemoizedScript::NativeScript(native) => {
93            let mut bytes = vec![
94                130, // CBOR definite array of length 2
95                0,   // Tag for Native Script
96            ];
97            bytes.extend_from_slice(native.original_bytes());
98            bytes
99        }
100        MemoizedScript::PlutusV1Script(plutus) => {
101            #[expect(clippy::unwrap_used)] // Infallible error.
102            cbor::to_vec((1, Into::<&ByteSlice>::into(plutus.as_ref()))).unwrap()
103        }
104        MemoizedScript::PlutusV2Script(plutus) => {
105            #[expect(clippy::unwrap_used)] // Infallible error.
106            cbor::to_vec((2, Into::<&ByteSlice>::into(plutus.as_ref()))).unwrap()
107        }
108        MemoizedScript::PlutusV3Script(plutus) => {
109            #[expect(clippy::unwrap_used)] // Infallible error.
110            cbor::to_vec((3, Into::<&ByteSlice>::into(plutus.as_ref()))).unwrap()
111        }
112    };
113
114    e.bytes(&buffer)?;
115
116    Ok(())
117}
118
119pub fn from_minted_script(wrapper: CborWrap<PseudoScript<KeepRaw<'_, NativeScript>>>) -> MemoizedScript {
120    match wrapper.0 {
121        PseudoScript::NativeScript(script) => MemoizedScript::NativeScript(MemoizedNativeScript::from(script)),
122        PseudoScript::PlutusV1Script(script) => MemoizedScript::PlutusV1Script(script),
123        PseudoScript::PlutusV2Script(script) => MemoizedScript::PlutusV2Script(script),
124        PseudoScript::PlutusV3Script(script) => MemoizedScript::PlutusV3Script(script),
125    }
126}
127
128impl TryFrom<PlaceholderScript> for MemoizedScript {
129    type Error = String;
130
131    fn try_from(placeholder: PlaceholderScript) -> Result<Self, Self::Error> {
132        Ok(match placeholder {
133            PlaceholderScript::NativeScript(bytes) => {
134                MemoizedScript::NativeScript(MemoizedNativeScript::try_from(bytes)?)
135            }
136            // FIXME: We should at least verify that the inner bytes are _plausible_ Plutus
137            // scripts. Not just gibberish. For V1, V2 and V3.
138            PlaceholderScript::PlutusV1(bytes) => MemoizedScript::PlutusV1Script(PlutusScript(bytes)),
139            PlaceholderScript::PlutusV2(bytes) => MemoizedScript::PlutusV2Script(PlutusScript(bytes)),
140            PlaceholderScript::PlutusV3(bytes) => MemoizedScript::PlutusV3Script(PlutusScript(bytes)),
141        })
142    }
143}
144
145// --------------------------------------------------------------------- Helpers
146
147#[derive(serde::Deserialize)]
148pub(crate) enum PlaceholderScript {
149    NativeScript(Bytes),
150    PlutusV1(Bytes),
151    PlutusV2(Bytes),
152    PlutusV3(Bytes),
153}