chia_sdk_types/
load_clvm.rs

1use std::{collections::HashMap, fs, io, path::Path, rc::Rc};
2
3use chialisp::{
4    classic::clvm_tools::clvmc::compile_clvm_text,
5    compiler::{compiler::DefaultCompilerOpts, comptypes::CompilerOpts},
6};
7use clvm_traits::ToClvmError;
8use clvm_utils::{TreeHash, tree_hash};
9use clvmr::{Allocator, error::EvalErr, serde::node_to_bytes};
10use rue_compiler::{Compiler, FileTree, normalize_path};
11use rue_options::find_project;
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum LoadClvmError {
16    #[error("IO error: {0}")]
17    Io(#[from] io::Error),
18
19    #[error("CLVM error: {0}")]
20    Clvm(#[from] EvalErr),
21
22    #[error("Invalid file name")]
23    InvalidFileName,
24
25    #[error("Compiler error: {0}")]
26    Compiler(String),
27
28    #[error("Conversion error: {0}")]
29    Conversion(#[from] ToClvmError),
30
31    #[error("Project error: {0}")]
32    Project(#[from] rue_options::Error),
33
34    #[error("Project not found")]
35    ProjectNotFound,
36
37    #[error("Main not found")]
38    MainNotFound,
39
40    #[error("Export not found: {0}")]
41    ExportNotFound(String),
42
43    #[error("Rue error: {0}")]
44    Rue(#[from] rue_compiler::Error),
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48pub struct Compilation {
49    pub reveal: Vec<u8>,
50    pub hash: TreeHash,
51}
52
53pub fn compile_chialisp(
54    path: &Path,
55    include_paths: &[String],
56) -> Result<Compilation, LoadClvmError> {
57    let mut allocator = Allocator::new();
58
59    let opts = Rc::new(DefaultCompilerOpts::new(
60        path.file_name()
61            .ok_or(LoadClvmError::InvalidFileName)?
62            .to_str()
63            .ok_or(LoadClvmError::InvalidFileName)?,
64    ))
65    .set_search_paths(include_paths);
66
67    let text = fs::read_to_string(path)?;
68
69    let ptr = compile_clvm_text(
70        &mut allocator,
71        opts,
72        &mut HashMap::new(),
73        &text,
74        path.to_str().ok_or(LoadClvmError::InvalidFileName)?,
75        false,
76    )
77    .map_err(|error| LoadClvmError::Compiler(format!("{error:?}")))?;
78
79    let hash = tree_hash(&allocator, ptr);
80    let reveal = node_to_bytes(&allocator, ptr)?;
81
82    Ok(Compilation { reveal, hash })
83}
84
85pub fn compile_rue(
86    path: &Path,
87    debug: bool,
88    export_name: Option<String>,
89) -> Result<Compilation, LoadClvmError> {
90    let mut allocator = Allocator::new();
91
92    let path = path.canonicalize()?;
93    let project = find_project(&path, debug)?;
94
95    let Some(project) = project else {
96        return Err(LoadClvmError::ProjectNotFound);
97    };
98
99    let main_kind = if project.entrypoint.join("main.rue").exists() {
100        Some(normalize_path(&project.entrypoint.join("main.rue"))?)
101    } else {
102        None
103    };
104
105    let mut ctx = Compiler::new(project.options);
106
107    let tree = FileTree::compile_path(&mut ctx, &project.entrypoint, &mut HashMap::new())?;
108
109    let ptr = if let Some(export_name) = export_name {
110        if let Some(export) = tree
111            .exports(
112                &mut ctx,
113                &mut allocator,
114                main_kind.as_ref(),
115                Some(&export_name),
116            )?
117            .into_iter()
118            .next()
119        {
120            export.ptr
121        } else {
122            return Err(LoadClvmError::ExportNotFound(export_name));
123        }
124    } else if let Some(main_kind) = main_kind
125        && let Some(main) = tree.main(&mut ctx, &mut allocator, &main_kind)?
126    {
127        main
128    } else if let Some(main) = tree.main(&mut ctx, &mut allocator, &normalize_path(&path)?)? {
129        main
130    } else {
131        return Err(LoadClvmError::MainNotFound);
132    };
133
134    let hash = tree_hash(&allocator, ptr);
135    let reveal = node_to_bytes(&allocator, ptr)?;
136
137    Ok(Compilation { reveal, hash })
138}
139
140#[macro_export]
141macro_rules! compile_chialisp {
142    ( $args:ty = $mod_name:ident, $path:literal ) => {
143        compile_chialisp!(impl $args = $mod_name $path);
144    };
145
146    ( impl $args:ty = $mod_name:ident $path:literal ) => {
147        static $mod_name: ::std::sync::LazyLock<Compilation> =
148            ::std::sync::LazyLock::new(|| $crate::compile_chialisp(::std::path::Path::new($path), &[".".to_string(), "include".to_string()]).unwrap());
149
150        impl $crate::Mod for $args {
151            fn mod_reveal() -> ::std::borrow::Cow<'static, [u8]> {
152                ::std::borrow::Cow::Owned($mod_name.reveal.clone())
153            }
154
155            fn mod_hash() -> $crate::__internals::TreeHash {
156                $mod_name.hash
157            }
158        }
159    };
160}
161
162#[macro_export]
163macro_rules! compile_rue {
164    ( $args:ty = $mod_name:ident, $path:literal ) => {
165        compile_rue!(impl $args = $mod_name $path false None);
166    };
167
168    ( $args:ty = $mod_name:ident, $path:literal, $export_name:literal ) => {
169        compile_rue!(impl $args = $mod_name $path false Some($export_name));
170    };
171
172    ( debug $args:ty = $mod_name:ident, $path:literal ) => {
173        compile_rue!(impl $args = $mod_name $path true None);
174    };
175
176    ( debug $args:ty = $mod_name:ident, $path:literal, $export_name:literal ) => {
177        compile_rue!(impl $args = $mod_name $path true Some($export_name));
178    };
179
180    ( impl $args:ty = $mod_name:ident $path:literal $debug:literal $export_name:expr ) => {
181        static $mod_name: ::std::sync::LazyLock<Compilation> =
182            ::std::sync::LazyLock::new(|| $crate::compile_rue(::std::path::Path::new($path), $debug, $export_name).unwrap());
183
184        impl $crate::Mod for $args {
185            fn mod_reveal() -> ::std::borrow::Cow<'static, [u8]> {
186                ::std::borrow::Cow::Owned($mod_name.reveal.clone())
187            }
188
189            fn mod_hash() -> $crate::__internals::TreeHash {
190                $mod_name.hash
191            }
192        }
193    };
194}
195
196#[cfg(test)]
197mod tests {
198    use clvm_traits::{FromClvm, ToClvm};
199    use clvm_utils::CurriedProgram;
200    use clvmr::{NodePtr, serde::node_from_bytes};
201
202    use crate::{Mod, run_puzzle};
203
204    use super::*;
205
206    #[test]
207    fn test_compile_chialisp() -> anyhow::Result<()> {
208        #[derive(Debug, Clone, PartialEq, Eq, Hash, ToClvm, FromClvm)]
209        #[clvm(curry)]
210        struct TestArgs {
211            a: u64,
212            b: u64,
213        }
214
215        compile_chialisp!(TestArgs = TEST_MOD, "compile_chialisp_test.clsp");
216
217        let args = TestArgs { a: 10, b: 20 };
218
219        let mut allocator = Allocator::new();
220
221        let mod_ptr = node_from_bytes(&mut allocator, TestArgs::mod_reveal().as_ref())?;
222
223        let ptr = CurriedProgram {
224            program: mod_ptr,
225            args,
226        }
227        .to_clvm(&mut allocator)?;
228
229        let output = run_puzzle(&mut allocator, ptr, NodePtr::NIL)?;
230
231        assert_eq!(hex::encode(node_to_bytes(&allocator, output)?), "8200e6");
232
233        Ok(())
234    }
235
236    #[test]
237    fn test_compile_rue() -> anyhow::Result<()> {
238        #[derive(Debug, Clone, PartialEq, Eq, Hash, ToClvm, FromClvm)]
239        #[clvm(curry)]
240        struct TestArgs {
241            a: u64,
242            b: u64,
243        }
244
245        compile_rue!(debug TestArgs = TEST_MOD, "compile_rue_test.rue");
246
247        let args = TestArgs { a: 10, b: 20 };
248
249        let mut allocator = Allocator::new();
250
251        let mod_ptr = node_from_bytes(&mut allocator, TestArgs::mod_reveal().as_ref())?;
252
253        let ptr = CurriedProgram {
254            program: mod_ptr,
255            args,
256        }
257        .to_clvm(&mut allocator)?;
258
259        let output = run_puzzle(&mut allocator, ptr, NodePtr::NIL)?;
260
261        assert_eq!(hex::encode(node_to_bytes(&allocator, output)?), "8200e6");
262
263        Ok(())
264    }
265}