chia-sdk-types 0.33.0

Standard Chia types for things such as puzzle info and conditions.
Documentation
use std::{collections::HashMap, fs, io, path::Path, rc::Rc};

use chialisp::{
    classic::clvm_tools::clvmc::compile_clvm_text,
    compiler::{compiler::DefaultCompilerOpts, comptypes::CompilerOpts},
};
use clvm_traits::ToClvmError;
use clvm_utils::{TreeHash, tree_hash};
use clvmr::{Allocator, error::EvalErr, serde::node_to_bytes};
use rue_compiler::{Compiler, FileTree, normalize_path};
use rue_options::find_project;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum LoadClvmError {
    #[error("IO error: {0}")]
    Io(#[from] io::Error),

    #[error("CLVM error: {0}")]
    Clvm(#[from] EvalErr),

    #[error("Invalid file name")]
    InvalidFileName,

    #[error("Compiler error: {0}")]
    Compiler(String),

    #[error("Conversion error: {0}")]
    Conversion(#[from] ToClvmError),

    #[error("Project error: {0}")]
    Project(#[from] rue_options::Error),

    #[error("Project not found")]
    ProjectNotFound,

    #[error("Main not found")]
    MainNotFound,

    #[error("Export not found: {0}")]
    ExportNotFound(String),

    #[error("Rue error: {0}")]
    Rue(#[from] rue_compiler::Error),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Compilation {
    pub reveal: Vec<u8>,
    pub hash: TreeHash,
}

pub fn compile_chialisp(
    path: &Path,
    include_paths: &[String],
) -> Result<Compilation, LoadClvmError> {
    let mut allocator = Allocator::new();

    let opts = Rc::new(DefaultCompilerOpts::new(
        path.file_name()
            .ok_or(LoadClvmError::InvalidFileName)?
            .to_str()
            .ok_or(LoadClvmError::InvalidFileName)?,
    ))
    .set_search_paths(include_paths);

    let text = fs::read_to_string(path)?;

    let ptr = compile_clvm_text(
        &mut allocator,
        opts,
        &mut HashMap::new(),
        &text,
        path.to_str().ok_or(LoadClvmError::InvalidFileName)?,
        false,
    )
    .map_err(|error| LoadClvmError::Compiler(format!("{error:?}")))?;

    let hash = tree_hash(&allocator, ptr);
    let reveal = node_to_bytes(&allocator, ptr)?;

    Ok(Compilation { reveal, hash })
}

pub fn compile_rue(
    path: &Path,
    debug: bool,
    export_name: Option<&str>,
) -> Result<Compilation, LoadClvmError> {
    let mut allocator = Allocator::new();

    let path = path.canonicalize()?;
    let project = find_project(&path, debug)?;

    let Some(project) = project else {
        return Err(LoadClvmError::ProjectNotFound);
    };

    let main_kind = if project.entrypoint.join("main.rue").exists() {
        Some(normalize_path(&project.entrypoint.join("main.rue"))?)
    } else {
        None
    };

    let mut ctx = Compiler::new(project.options);

    let tree = FileTree::compile_path(&mut ctx, &project.entrypoint, &mut HashMap::new())?;

    let ptr = if let Some(export_name) = export_name {
        if let Some(export) = tree
            .exports(
                &mut ctx,
                &mut allocator,
                main_kind.as_ref(),
                Some(export_name),
            )?
            .into_iter()
            .next()
        {
            export.ptr
        } else {
            return Err(LoadClvmError::ExportNotFound(export_name.to_string()));
        }
    } else if let Some(main_kind) = main_kind
        && let Some(main) = tree.main(&mut ctx, &mut allocator, &main_kind)?
    {
        main
    } else if let Some(main) = tree.main(&mut ctx, &mut allocator, &normalize_path(&path)?)? {
        main
    } else {
        return Err(LoadClvmError::MainNotFound);
    };

    let hash = tree_hash(&allocator, ptr);
    let reveal = node_to_bytes(&allocator, ptr)?;

    Ok(Compilation { reveal, hash })
}

#[macro_export]
macro_rules! compile_chialisp {
    ( $args:ty = $mod_name:ident, $path:literal ) => {
        compile_chialisp!(impl $args = $mod_name $path);
    };

    ( impl $args:ty = $mod_name:ident $path:literal ) => {
        static $mod_name: ::std::sync::LazyLock<Compilation> =
            ::std::sync::LazyLock::new(|| $crate::compile_chialisp(::std::path::Path::new($path), &[".".to_string(), "include".to_string()]).unwrap());

        impl $crate::Mod for $args {
            fn mod_reveal() -> ::std::borrow::Cow<'static, [u8]> {
                ::std::borrow::Cow::Owned($mod_name.reveal.clone())
            }

            fn mod_hash() -> $crate::__internals::TreeHash {
                $mod_name.hash
            }
        }
    };
}

#[macro_export]
macro_rules! compile_rue {
    ( $args:ty = $mod_name:ident, $path:literal ) => {
        compile_rue!(impl $args = $mod_name $path false None);
    };

    ( $args:ty = $mod_name:ident, $path:literal, $export_name:literal ) => {
        compile_rue!(impl $args = $mod_name $path false Some($export_name));
    };

    ( debug $args:ty = $mod_name:ident, $path:literal ) => {
        compile_rue!(impl $args = $mod_name $path true None);
    };

    ( debug $args:ty = $mod_name:ident, $path:literal, $export_name:literal ) => {
        compile_rue!(impl $args = $mod_name $path true Some($export_name));
    };

    ( impl $args:ty = $mod_name:ident $path:literal $debug:literal $export_name:expr ) => {
        static $mod_name: ::std::sync::LazyLock<Compilation> =
            ::std::sync::LazyLock::new(|| $crate::compile_rue(::std::path::Path::new($path), $debug, $export_name).unwrap());

        impl $crate::Mod for $args {
            fn mod_reveal() -> ::std::borrow::Cow<'static, [u8]> {
                ::std::borrow::Cow::Owned($mod_name.reveal.clone())
            }

            fn mod_hash() -> $crate::__internals::TreeHash {
                $mod_name.hash
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use clvm_traits::{FromClvm, ToClvm};
    use clvm_utils::CurriedProgram;
    use clvmr::{NodePtr, serde::node_from_bytes};

    use crate::{Mod, run_puzzle};

    use super::*;

    #[test]
    fn test_compile_chialisp() -> anyhow::Result<()> {
        #[derive(Debug, Clone, PartialEq, Eq, Hash, ToClvm, FromClvm)]
        #[clvm(curry)]
        struct TestArgs {
            a: u64,
            b: u64,
        }

        compile_chialisp!(TestArgs = TEST_MOD, "compile_chialisp_test.clsp");

        let args = TestArgs { a: 10, b: 20 };

        let mut allocator = Allocator::new();

        let mod_ptr = node_from_bytes(&mut allocator, TestArgs::mod_reveal().as_ref())?;

        let ptr = CurriedProgram {
            program: mod_ptr,
            args,
        }
        .to_clvm(&mut allocator)?;

        let output = run_puzzle(&mut allocator, ptr, NodePtr::NIL)?;

        assert_eq!(hex::encode(node_to_bytes(&allocator, output)?), "8200e6");

        Ok(())
    }

    #[test]
    fn test_compile_rue() -> anyhow::Result<()> {
        #[derive(Debug, Clone, PartialEq, Eq, Hash, ToClvm, FromClvm)]
        #[clvm(curry)]
        struct TestArgs {
            a: u64,
            b: u64,
        }

        compile_rue!(debug TestArgs = TEST_MOD, "compile_rue_test.rue");

        let args = TestArgs { a: 10, b: 20 };

        let mut allocator = Allocator::new();

        let mod_ptr = node_from_bytes(&mut allocator, TestArgs::mod_reveal().as_ref())?;

        let ptr = CurriedProgram {
            program: mod_ptr,
            args,
        }
        .to_clvm(&mut allocator)?;

        let output = run_puzzle(&mut allocator, ptr, NodePtr::NIL)?;

        assert_eq!(hex::encode(node_to_bytes(&allocator, output)?), "8200e6");

        Ok(())
    }

    #[test]
    fn test_compile_rue_export() -> anyhow::Result<()> {
        #[derive(Debug, Clone, PartialEq, Eq, Hash, ToClvm, FromClvm)]
        #[clvm(curry)]
        struct TestArgs {
            a: u64,
            b: u64,
        }

        compile_rue!(debug TestArgs = TEST_MOD, "compile_rue_test.rue", "another");

        let args = TestArgs { a: 10, b: 20 };

        let mut allocator = Allocator::new();

        let mod_ptr = node_from_bytes(&mut allocator, TestArgs::mod_reveal().as_ref())?;

        let ptr = CurriedProgram {
            program: mod_ptr,
            args,
        }
        .to_clvm(&mut allocator)?;

        let output = run_puzzle(&mut allocator, ptr, NodePtr::NIL)?;

        assert_eq!(hex::encode(node_to_bytes(&allocator, output)?), "8200e6");

        Ok(())
    }
}