partner_chains_cardano_offchain/
plutus_script.rs1use crate::csl::*;
2use anyhow::{Context, Error, anyhow};
3use cardano_serialization_lib::{
4 Address, JsError, Language, LanguageKind, NetworkIdKind, PlutusData, ScriptHash,
5};
6use plutus::ToDatum;
7use sidechain_domain::{AssetId, AssetName, PolicyId};
8use std::marker::PhantomData;
9use uplc::ast::{DeBruijn, Program};
10
11#[derive(Clone, PartialEq, Eq)]
13pub struct PlutusScript {
14 pub bytes: Vec<u8>,
16 pub language: Language,
18}
19
20impl std::fmt::Debug for PlutusScript {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 f.debug_struct("PlutusScript")
23 .field("bytes", &hex::encode(&self.bytes))
24 .field("language", &self.language.kind())
25 .finish()
26 }
27}
28
29impl PlutusScript {
30 pub fn from_cbor(cbor: &[u8], language: Language) -> Self {
32 Self { bytes: cbor.into(), language }
33 }
34
35 pub fn from_wrapped_cbor(plutus_script_raw_cbor: &[u8]) -> anyhow::Result<Self> {
40 let plutus_script_bytes: uplc::PlutusData = minicbor::decode(plutus_script_raw_cbor)?;
41 let plutus_script_bytes = match plutus_script_bytes {
42 uplc::PlutusData::BoundedBytes(bb) => Ok(bb),
43 _ => Err(anyhow!("expected validator raw to be BoundedBytes")),
44 }?;
45 Ok(Self::from_cbor(&plutus_script_bytes, Language::new_plutus_v2()))
46 }
47
48 pub fn apply_data_uplc(self, data: uplc::PlutusData) -> Result<Self, anyhow::Error> {
54 let mut buffer = Vec::new();
55 let mut program = Program::<DeBruijn>::from_cbor(&self.bytes, &mut buffer)
56 .map_err(|e| anyhow!(e.to_string()))?;
57 program = program.apply_data(data);
58 let bytes = program
59 .to_cbor()
60 .map_err(|_| anyhow!("Couldn't encode resulting script as CBOR."))?;
61 Ok(Self { bytes, ..self })
62 }
63
64 pub fn unapply_data_uplc(&self) -> anyhow::Result<uplc::PlutusData> {
68 let mut buffer = Vec::new();
69 let program = Program::<DeBruijn>::from_cbor(&self.bytes, &mut buffer).unwrap();
70 match program.term {
71 uplc::ast::Term::Apply { function: _, argument } => {
72 let res: Result<uplc::PlutusData, String> = (*argument).clone().try_into();
73 res.map_err(|e| anyhow!(e))
74 },
75 _ => Err(anyhow!("Given Plutus Script is not an applied term")),
76 }
77 }
78
79 pub fn unapply_data_csl(&self) -> Result<PlutusData, anyhow::Error> {
82 let uplc_pd = self.unapply_data_uplc()?;
83 let cbor_bytes = minicbor::to_vec(uplc_pd).expect("to_vec has Infallible error type");
84 Ok(PlutusData::from_bytes(cbor_bytes).expect("UPLC encoded PlutusData is valid"))
85 }
86
87 pub fn address(&self, network: NetworkIdKind) -> Address {
89 script_address(&self.bytes, network, self.language)
90 }
91
92 pub fn address_data(&self, network: NetworkIdKind) -> anyhow::Result<uplc::PlutusData> {
95 let mut se = cbor_event::se::Serializer::new_vec();
96 cbor_event::se::Serialize::serialize(
97 &PlutusData::from_address(&self.address(network))?,
98 &mut se,
99 )
100 .map_err(|e| anyhow!(e))?;
101 let bytes = se.finalize();
102 minicbor::decode(&bytes).map_err(|e| anyhow!(e.to_string()))
103 }
104
105 pub fn address_bech32(&self, network: NetworkIdKind) -> anyhow::Result<String> {
107 self.address(network)
108 .to_bech32(None)
109 .context("Converting script address to bech32")
110 }
111
112 pub fn script_hash(&self) -> [u8; 28] {
114 plutus_script_hash(&self.bytes, self.language)
115 }
116
117 pub fn csl_script_hash(&self) -> ScriptHash {
119 ScriptHash::from(self.script_hash())
120 }
121
122 pub fn policy_id(&self) -> PolicyId {
124 PolicyId(self.script_hash())
125 }
126
127 pub fn empty_name_asset(&self) -> AssetId {
129 AssetId { policy_id: self.policy_id(), asset_name: AssetName::empty() }
130 }
131
132 pub fn asset(
134 &self,
135 asset_name: cardano_serialization_lib::AssetName,
136 ) -> Result<AssetId, JsError> {
137 Ok(AssetId { policy_id: self.policy_id(), asset_name: AssetName::from_csl(asset_name)? })
138 }
139
140 pub fn to_csl(&self) -> cardano_serialization_lib::PlutusScript {
142 match self.language.kind() {
143 LanguageKind::PlutusV1 => {
144 cardano_serialization_lib::PlutusScript::new(self.bytes.clone())
145 },
146 LanguageKind::PlutusV2 => {
147 cardano_serialization_lib::PlutusScript::new_v2(self.bytes.clone())
148 },
149 LanguageKind::PlutusV3 => {
150 cardano_serialization_lib::PlutusScript::new_v3(self.bytes.clone())
151 },
152 }
153 }
154}
155
156impl TryFrom<ogmios_client::types::OgmiosScript> for PlutusScript {
157 type Error = Error;
158
159 fn try_from(script: ogmios_client::types::OgmiosScript) -> Result<Self, Self::Error> {
160 let language = match script.language.as_str() {
161 "plutus:v1" => Language::new_plutus_v1(),
162 "plutus:v2" => Language::new_plutus_v2(),
163 "plutus:v3" => Language::new_plutus_v3(),
164 _ => return Err(anyhow!("Unsupported Plutus language version: {}", script.language)),
165 };
166 Ok(Self::from_cbor(&script.cbor, language))
167 }
168}
169
170impl From<PlutusScript> for ogmios_client::types::OgmiosScript {
171 fn from(val: PlutusScript) -> Self {
172 ogmios_client::types::OgmiosScript {
173 language: match val.language.kind() {
174 LanguageKind::PlutusV1 => "plutus:v1",
175 LanguageKind::PlutusV2 => "plutus:v2",
176 LanguageKind::PlutusV3 => "plutus:v3",
177 }
178 .to_string(),
179 cbor: val.bytes,
180 json: None,
181 }
182 }
183}
184
185impl From<raw_scripts::RawScript> for PlutusScript {
186 fn from(value: raw_scripts::RawScript) -> Self {
187 PlutusScript::from_wrapped_cbor(value.0).expect("raw_scripts provides valid scripts")
188 }
189}
190
191#[macro_export]
203macro_rules! plutus_script {
204 ($ps:expr $(,$args:expr)*) => (
205 {
206 let script = $crate::plutus_script::PlutusScript::from($ps);
207 plutus_script!(@inner, script $(,$args)*)
208 }
209 );
210 (@inner, $ps:expr) => (Ok($ps));
211 (@inner, $ps:expr, $arg:expr $(,$args:expr)*) => (
212 $ps.apply_data_uplc($crate::plutus_script::PlutusDataWrapper::from($arg).0)
213 .and_then(|ps| plutus_script!(@inner, ps $(,$args)*))
214 )
215}
216
217pub struct PlutusDataWrapper<T>(pub uplc::PlutusData, PhantomData<T>);
222
223impl<T> PlutusDataWrapper<T> {
224 pub fn new(d: uplc::PlutusData) -> Self {
226 Self(d, PhantomData)
227 }
228}
229
230impl From<uplc::PlutusData> for PlutusDataWrapper<()> {
231 fn from(value: uplc::PlutusData) -> Self {
232 PlutusDataWrapper::new(value)
233 }
234}
235
236impl<T: ToDatum> From<T> for PlutusDataWrapper<T> {
237 fn from(value: T) -> Self {
238 PlutusDataWrapper::new(to_plutus_data(value.to_datum()))
239 }
240}
241
242impl From<raw_scripts::ScriptId> for PlutusDataWrapper<()> {
243 fn from(value: raw_scripts::ScriptId) -> Self {
244 PlutusDataWrapper::new(to_plutus_data((value as u32).to_datum()))
245 }
246}
247
248fn to_plutus_data(datum: plutus::Datum) -> uplc::PlutusData {
249 uplc::plutus_data(&minicbor::to_vec(datum).expect("to_vec is Infallible"))
250 .expect("transformation from PC Datum to pallas PlutusData can't fail")
251}
252
253#[cfg(test)]
254pub(crate) mod tests {
255 use super::*;
256 use hex_literal::hex;
257 use raw_scripts::RawScript;
258 use sidechain_domain::{McTxHash, UtxoId, UtxoIndex};
259
260 pub(crate) const TEST_GENESIS_UTXO: UtxoId =
261 UtxoId { tx_hash: McTxHash([0u8; 32]), index: UtxoIndex(0) };
262
263 pub(crate) const CANDIDATES_SCRIPT_RAW: RawScript = RawScript(&hex!(
265 "59013b590138010000323322323322323232322222533553353232323233012225335001100f2215333573466e3c014dd7001080909802000980798051bac330033530040022200148040dd7198011a980180311000a4010660026a600400644002900019112999ab9a33710002900009805a4810350543600133003001002300f22253350011300b49103505437002215333573466e1d20000041002133005337020089001000919199109198008018011aab9d001300735573c0026ea80044028402440204c01d2401035054350030092233335573e0024016466a0146ae84008c00cd5d100124c6010446666aae7c00480288cd4024d5d080118019aba20024988c98cd5ce00080109000891001091000980191299a800880211099a80280118020008910010910911980080200191918008009119801980100100081"
266 ));
267
268 pub(crate) const CANDIDATES_SCRIPT_WITH_APPLIED_PARAMS: [u8; 362] = hex!(
270 "5901670100003323322323322323232322222533553353232323233012225335001100f2215333573466e3c014dd7001080909802000980798051bac330033530040022200148040dd7198011a980180311000a4010660026a600400644002900019112999ab9a33710002900009805a490350543600133003001002300f22253350011300b49103505437002215333573466e1d20000041002133005337020089001000919199109198008018011aab9d001300735573c0026ea80044028402440204c01d2401035054350030092233335573e0024016466a0146ae84008c00cd5d100124c6010446666aae7c00480288cd4024d5d080118019aba20024988c98cd5ce00080109000891001091000980191299a800880211099a802801180200089100109109119800802001919180080091198019801001000a6012bd8799fd8799f58200000000000000000000000000000000000000000000000000000000000000000ff00ff0001"
271 );
272
273 #[test]
274 fn apply_parameters_to_deregister() {
275 let applied = plutus_script![CANDIDATES_SCRIPT_RAW, TEST_GENESIS_UTXO].unwrap();
276 assert_eq!(hex::encode(applied.bytes), hex::encode(CANDIDATES_SCRIPT_WITH_APPLIED_PARAMS));
277 }
278
279 #[test]
280 fn unapply_term_csl() {
281 let applied = plutus_script![CANDIDATES_SCRIPT_RAW, TEST_GENESIS_UTXO].unwrap();
282 let data: PlutusData = applied.unapply_data_csl().unwrap();
283 assert_eq!(
284 data,
285 PlutusData::from_bytes(minicbor::to_vec(TEST_GENESIS_UTXO.to_datum()).unwrap())
286 .unwrap()
287 )
288 }
289
290 #[test]
291 fn unapply_term_uplc() {
292 let applied = plutus_script![CANDIDATES_SCRIPT_RAW, TEST_GENESIS_UTXO].unwrap();
293 let data: uplc::PlutusData = applied.unapply_data_uplc().unwrap();
294 assert_eq!(
295 data,
296 uplc::plutus_data(&minicbor::to_vec(TEST_GENESIS_UTXO.to_datum()).unwrap()).unwrap()
297 )
298 }
299}