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 v2_from_cbor(plutus_script_bytes: &[u8]) -> anyhow::Result<Self> {
37 Ok(Self::from_cbor(&plutus_script_bytes, Language::new_plutus_v2()))
38 }
39
40 pub fn apply_data_uplc(self, data: uplc::PlutusData) -> Result<Self, anyhow::Error> {
46 let mut buffer = Vec::new();
47 let mut program = Program::<DeBruijn>::from_cbor(&self.bytes, &mut buffer)
48 .map_err(|e| anyhow!(e.to_string()))?;
49 program = program.apply_data(data);
50 let bytes = program
51 .to_cbor()
52 .map_err(|_| anyhow!("Couldn't encode resulting script as CBOR."))?;
53 Ok(Self { bytes, ..self })
54 }
55
56 pub fn unapply_data_uplc(&self) -> anyhow::Result<uplc::PlutusData> {
60 let mut buffer = Vec::new();
61 let program = Program::<DeBruijn>::from_cbor(&self.bytes, &mut buffer).unwrap();
62 match program.term {
63 uplc::ast::Term::Apply { function: _, argument } => {
64 let res: Result<uplc::PlutusData, String> = (*argument).clone().try_into();
65 res.map_err(|e| anyhow!(e))
66 },
67 _ => Err(anyhow!("Given Plutus Script is not an applied term")),
68 }
69 }
70
71 pub fn unapply_data_csl(&self) -> Result<PlutusData, anyhow::Error> {
74 let uplc_pd = self.unapply_data_uplc()?;
75 let cbor_bytes = minicbor::to_vec(uplc_pd).expect("to_vec has Infallible error type");
76 Ok(PlutusData::from_bytes(cbor_bytes).expect("UPLC encoded PlutusData is valid"))
77 }
78
79 pub fn address(&self, network: NetworkIdKind) -> Address {
81 script_address(&self.bytes, network, self.language)
82 }
83
84 pub fn address_data(&self, network: NetworkIdKind) -> anyhow::Result<uplc::PlutusData> {
87 let mut se = cbor_event::se::Serializer::new_vec();
88 cbor_event::se::Serialize::serialize(
89 &PlutusData::from_address(&self.address(network))?,
90 &mut se,
91 )
92 .map_err(|e| anyhow!(e))?;
93 let bytes = se.finalize();
94 minicbor::decode(&bytes).map_err(|e| anyhow!(e.to_string()))
95 }
96
97 pub fn address_bech32(&self, network: NetworkIdKind) -> anyhow::Result<String> {
99 self.address(network)
100 .to_bech32(None)
101 .context("Converting script address to bech32")
102 }
103
104 pub fn script_hash(&self) -> [u8; 28] {
106 plutus_script_hash(&self.bytes, self.language)
107 }
108
109 pub fn csl_script_hash(&self) -> ScriptHash {
111 ScriptHash::from(self.script_hash())
112 }
113
114 pub fn policy_id(&self) -> PolicyId {
116 PolicyId(self.script_hash())
117 }
118
119 pub fn empty_name_asset(&self) -> AssetId {
121 AssetId { policy_id: self.policy_id(), asset_name: AssetName::empty() }
122 }
123
124 pub fn asset(
126 &self,
127 asset_name: cardano_serialization_lib::AssetName,
128 ) -> Result<AssetId, JsError> {
129 Ok(AssetId { policy_id: self.policy_id(), asset_name: AssetName::from_csl(asset_name)? })
130 }
131
132 pub fn to_csl(&self) -> cardano_serialization_lib::PlutusScript {
134 match self.language.kind() {
135 LanguageKind::PlutusV1 => {
136 cardano_serialization_lib::PlutusScript::new(self.bytes.clone())
137 },
138 LanguageKind::PlutusV2 => {
139 cardano_serialization_lib::PlutusScript::new_v2(self.bytes.clone())
140 },
141 LanguageKind::PlutusV3 => {
142 cardano_serialization_lib::PlutusScript::new_v3(self.bytes.clone())
143 },
144 }
145 }
146}
147
148impl TryFrom<ogmios_client::types::OgmiosScript> for PlutusScript {
149 type Error = Error;
150
151 fn try_from(script: ogmios_client::types::OgmiosScript) -> Result<Self, Self::Error> {
152 let language = match script.language.as_str() {
153 "plutus:v1" => Language::new_plutus_v1(),
154 "plutus:v2" => Language::new_plutus_v2(),
155 "plutus:v3" => Language::new_plutus_v3(),
156 _ => return Err(anyhow!("Unsupported Plutus language version: {}", script.language)),
157 };
158 Ok(Self::from_cbor(&script.cbor, language))
159 }
160}
161
162impl From<PlutusScript> for ogmios_client::types::OgmiosScript {
163 fn from(val: PlutusScript) -> Self {
164 ogmios_client::types::OgmiosScript {
165 language: match val.language.kind() {
166 LanguageKind::PlutusV1 => "plutus:v1",
167 LanguageKind::PlutusV2 => "plutus:v2",
168 LanguageKind::PlutusV3 => "plutus:v3",
169 }
170 .to_string(),
171 cbor: val.bytes,
172 json: None,
173 }
174 }
175}
176
177impl From<raw_scripts::RawScript> for PlutusScript {
178 fn from(value: raw_scripts::RawScript) -> Self {
179 PlutusScript::v2_from_cbor(value.0).expect("raw_scripts provides valid scripts")
180 }
181}
182
183#[macro_export]
195macro_rules! plutus_script {
196 ($ps:expr $(,$args:expr)*) => (
197 {
198 let script = $crate::plutus_script::PlutusScript::from($ps);
199 plutus_script!(@inner, script $(,$args)*)
200 }
201 );
202 (@inner, $ps:expr) => (Ok::<crate::plutus_script::PlutusScript, anyhow::Error>($ps));
203 (@inner, $ps:expr, $arg:expr $(,$args:expr)*) => (
204 $ps.apply_data_uplc($crate::plutus_script::PlutusDataWrapper::from($arg).0)
205 .and_then(|ps| plutus_script!(@inner, ps $(,$args)*))
206 )
207}
208
209pub struct PlutusDataWrapper<T>(pub uplc::PlutusData, PhantomData<T>);
214
215impl<T> PlutusDataWrapper<T> {
216 pub fn new(d: uplc::PlutusData) -> Self {
218 Self(d, PhantomData)
219 }
220}
221
222impl From<uplc::PlutusData> for PlutusDataWrapper<()> {
223 fn from(value: uplc::PlutusData) -> Self {
224 PlutusDataWrapper::new(value)
225 }
226}
227
228impl<T: ToDatum> From<T> for PlutusDataWrapper<T> {
229 fn from(value: T) -> Self {
230 PlutusDataWrapper::new(to_plutus_data(value.to_datum()))
231 }
232}
233
234impl From<raw_scripts::ScriptId> for PlutusDataWrapper<()> {
235 fn from(value: raw_scripts::ScriptId) -> Self {
236 PlutusDataWrapper::new(to_plutus_data((value as u32).to_datum()))
237 }
238}
239
240fn to_plutus_data(datum: plutus::Datum) -> uplc::PlutusData {
241 uplc::plutus_data(&minicbor::to_vec(datum).expect("to_vec is Infallible"))
242 .expect("transformation from PC Datum to pallas PlutusData can't fail")
243}
244
245#[cfg(test)]
246pub(crate) mod tests {
247 use super::*;
248 use hex_literal::hex;
249 use raw_scripts::RawScript;
250 use sidechain_domain::{McTxHash, UtxoId, UtxoIndex};
251
252 pub(crate) const TEST_GENESIS_UTXO: UtxoId =
253 UtxoId { tx_hash: McTxHash([0u8; 32]), index: UtxoIndex(0) };
254
255 pub(crate) const CANDIDATES_SCRIPT_RAW: RawScript = RawScript(&hex!(
257 "59013b590138010000323322323322323232322222533553353232323233012225335001100f2215333573466e3c014dd7001080909802000980798051bac330033530040022200148040dd7198011a980180311000a4010660026a600400644002900019112999ab9a33710002900009805a4810350543600133003001002300f22253350011300b49103505437002215333573466e1d20000041002133005337020089001000919199109198008018011aab9d001300735573c0026ea80044028402440204c01d2401035054350030092233335573e0024016466a0146ae84008c00cd5d100124c6010446666aae7c00480288cd4024d5d080118019aba20024988c98cd5ce00080109000891001091000980191299a800880211099a80280118020008910010910911980080200191918008009119801980100100081"
258 ));
259
260 pub(crate) const CANDIDATES_SCRIPT_WITH_APPLIED_PARAMS: &[u8] = &hex!(
262 "583559013830104c012bd8799fd8799f58200000000000000000000000000000000000000000000000000000000000000000ff00ff0001"
263 );
264
265 #[test]
266 fn apply_parameters_to_deregister() {
267 let applied = plutus_script![CANDIDATES_SCRIPT_RAW, TEST_GENESIS_UTXO].unwrap();
268 assert_eq!(hex::encode(applied.bytes), hex::encode(CANDIDATES_SCRIPT_WITH_APPLIED_PARAMS));
269 }
270
271 #[test]
272 fn unapply_term_csl() {
273 let applied = plutus_script![CANDIDATES_SCRIPT_RAW, TEST_GENESIS_UTXO].unwrap();
274 let data: PlutusData = applied.unapply_data_csl().unwrap();
275 assert_eq!(
276 data,
277 PlutusData::from_bytes(minicbor::to_vec(TEST_GENESIS_UTXO.to_datum()).unwrap())
278 .unwrap()
279 )
280 }
281
282 #[test]
283 fn unapply_term_uplc() {
284 let applied = plutus_script![CANDIDATES_SCRIPT_RAW, TEST_GENESIS_UTXO].unwrap();
285 let data: uplc::PlutusData = applied.unapply_data_uplc().unwrap();
286 assert_eq!(
287 data,
288 uplc::plutus_data(&minicbor::to_vec(TEST_GENESIS_UTXO.to_datum()).unwrap()).unwrap()
289 )
290 }
291}