diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index c003252d..301e7662 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -1,6 +1,20 @@ -use std::{fmt::Display, rc::Rc}; +use std::{ + fmt::{self, Display}, + rc::Rc, +}; -use pallas_primitives::{alonzo::PlutusData, babbage::Language}; +use serde::{ + self, + de::{self, Deserialize, Deserializer, MapAccess, Visitor}, + ser::{Serialize, SerializeStruct, Serializer}, +}; + +use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart}; +use pallas_primitives::{ + alonzo::PlutusData, + babbage::{self as cardano, Language}, +}; +use pallas_traverse::ComputeHash; use crate::{ builtins::DefaultFunction, @@ -79,6 +93,81 @@ where } } +impl Serialize for Program { + fn serialize(&self, serializer: S) -> Result { + let cbor = self.to_cbor().unwrap(); + let mut s = serializer.serialize_struct("Program", 2)?; + s.serialize_field("compiledCode", &hex::encode(&cbor))?; + s.serialize_field("hash", &cardano::PlutusV2Script(cbor.into()).compute_hash())?; + s.end() + } +} + +impl<'a> Deserialize<'a> for Program { + fn deserialize>(deserializer: D) -> Result { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "camelCase")] + enum Fields { + CompiledCode, + } + + struct ProgramVisitor; + + impl<'a> Visitor<'a> for ProgramVisitor { + type Value = Program; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Program") + } + + fn visit_map(self, mut map: V) -> Result, V::Error> + where + V: MapAccess<'a>, + { + let mut compiled_code: Option = None; + while let Some(key) = map.next_key()? { + match key { + Fields::CompiledCode => { + if compiled_code.is_some() { + return Err(de::Error::duplicate_field("compiledCode")); + } + compiled_code = Some(map.next_value()?); + } + } + } + let compiled_code = + compiled_code.ok_or_else(|| de::Error::missing_field("compiledCode"))?; + + let mut cbor_buffer = Vec::new(); + let mut flat_buffer = Vec::new(); + + Program::::from_hex(&compiled_code, &mut cbor_buffer, &mut flat_buffer) + .map_err(|e| { + de::Error::invalid_value( + de::Unexpected::Other(&format!("{}", e)), + &"a base16-encoded CBOR-serialized UPLC program", + ) + }) + } + } + + const FIELDS: &[&str] = &["compiledCode"]; + deserializer.deserialize_struct("Program", FIELDS, ProgramVisitor) + } +} + +impl Program { + pub fn address(&self, network: Network, delegation: ShelleyDelegationPart) -> ShelleyAddress { + let cbor = self.to_cbor().unwrap(); + let validator_hash = cardano::PlutusV2Script(cbor.into()).compute_hash(); + ShelleyAddress::new( + network, + ShelleyPaymentPart::Script(validator_hash), + delegation, + ) + } +} + /// This represents a term in Untyped Plutus Core. /// We need a generic type for the different forms that a program may be in. /// Specifically, `Var` and `parameter_name` in `Lambda` can be a `Name`,