use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::Display;
use core::num::TryFromIntError;
use miden_core::mast::MastNodeExt;
use miden_crypto_derive::WordWrapper;
use miden_mast_package::Package;
use miden_mast_package::debug_info::PackageDebugInfo;
use miden_processor::LoadedMastForest;
use super::Felt;
use crate::assembly::mast::{MastForest, MastNodeId};
use crate::assembly::{Library, Path};
use crate::errors::NoteError;
use crate::package::{loaded_mast_forest, package_debug_info};
use crate::utils::create_external_node_forest;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::vm::{AdviceMap, Program};
use crate::{PrettyPrint, Word};
const NOTE_SCRIPT_ATTRIBUTE: &str = "note_script";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
pub struct NoteScriptRoot(Word);
impl From<NoteScriptRoot> for Word {
fn from(root: NoteScriptRoot) -> Self {
root.0
}
}
impl Display for NoteScriptRoot {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Serializable for NoteScriptRoot {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write(self.0);
}
fn get_size_hint(&self) -> usize {
self.0.get_size_hint()
}
}
impl Deserializable for NoteScriptRoot {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let word: Word = source.read()?;
Ok(Self::from_raw(word))
}
}
#[derive(Debug, Clone)]
pub struct NoteScript {
mast: Arc<MastForest>,
entrypoint: MastNodeId,
package_debug_info: Option<Arc<PackageDebugInfo>>,
}
impl NoteScript {
pub fn new(code: Program) -> Self {
Self {
entrypoint: code.entrypoint(),
mast: code.mast_forest().clone(),
package_debug_info: None,
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoteError> {
Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)
}
pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
assert!(mast.get_node_by_id(entrypoint).is_some());
Self {
mast,
entrypoint,
package_debug_info: None,
}
}
pub fn from_library(library: &Library) -> Result<Self, NoteError> {
let mut entrypoint = None;
for export in library.manifest.exports() {
if let Some(proc_export) = export.as_procedure() {
if proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
if entrypoint.is_some() {
return Err(NoteError::NoteScriptMultipleProceduresWithAttribute);
}
entrypoint = Some(
proc_export.node.ok_or(NoteError::NoteScriptNoProcedureWithAttribute)?,
);
}
}
}
let entrypoint = entrypoint.ok_or(NoteError::NoteScriptNoProcedureWithAttribute)?;
Ok(Self {
mast: library.mast_forest().clone(),
entrypoint,
package_debug_info: package_debug_info(library),
})
}
pub fn from_library_reference(library: &Library, path: &Path) -> Result<Self, NoteError> {
let export = library
.manifest
.exports()
.find(|e| e.path().as_ref() == path)
.ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
let proc_export = export
.as_procedure()
.ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
if !proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
return Err(NoteError::NoteScriptProcedureMissingAttribute(path.to_string().into()));
}
let digest = proc_export.digest;
let (mast, entrypoint) = create_external_node_forest(digest);
Ok(Self {
mast: Arc::new(mast),
entrypoint,
package_debug_info: package_debug_info(library),
})
}
pub fn from_package(package: &Package) -> Result<Self, NoteError> {
Ok(NoteScript::from_library(package))?
}
pub fn root(&self) -> NoteScriptRoot {
NoteScriptRoot::from_raw(self.mast[self.entrypoint].digest())
}
pub fn mast(&self) -> Arc<MastForest> {
self.mast.clone()
}
pub fn loaded_mast_forest(&self) -> LoadedMastForest {
loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
}
pub fn entrypoint(&self) -> MastNodeId {
self.entrypoint
}
pub fn clear_debug_info(&mut self) {
self.package_debug_info = None;
}
pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
if advice_map.is_empty() {
return self;
}
let mast = (*self.mast).clone().with_advice_map(advice_map);
Self {
mast: Arc::new(mast),
entrypoint: self.entrypoint,
package_debug_info: self.package_debug_info,
}
}
}
impl PartialEq for NoteScript {
fn eq(&self, other: &Self) -> bool {
self.mast == other.mast && self.entrypoint == other.entrypoint
}
}
impl Eq for NoteScript {}
impl From<&NoteScript> for Vec<Felt> {
fn from(script: &NoteScript) -> Self {
let mut bytes = script.mast.to_bytes();
let len = bytes.len();
let missing = if !len.is_multiple_of(4) { 4 - (len % 4) } else { 0 };
bytes.resize(bytes.len() + missing, 0);
let final_size = 2 + bytes.len();
let mut result = Vec::with_capacity(final_size);
result.push(Felt::from(u32::from(script.entrypoint)));
result.push(Felt::new_unchecked(len as u64));
let mut encoded: &[u8] = &bytes;
while encoded.len() >= 4 {
let (data, rest) =
encoded.split_first_chunk::<4>().expect("The length has been checked");
let number = u32::from_le_bytes(*data);
result.push(Felt::from(number));
encoded = rest;
}
result
}
}
impl From<NoteScript> for Vec<Felt> {
fn from(value: NoteScript) -> Self {
(&value).into()
}
}
impl AsRef<NoteScript> for NoteScript {
fn as_ref(&self) -> &NoteScript {
self
}
}
impl TryFrom<&[Felt]> for NoteScript {
type Error = DeserializationError;
fn try_from(elements: &[Felt]) -> Result<Self, Self::Error> {
if elements.len() < 2 {
return Err(DeserializationError::UnexpectedEOF);
}
let entrypoint: u32 = elements[0]
.as_canonical_u64()
.try_into()
.map_err(|err: TryFromIntError| DeserializationError::InvalidValue(err.to_string()))?;
let len = elements[1].as_canonical_u64();
let mut data = Vec::with_capacity(elements.len() * 4);
for &felt in &elements[2..] {
let element: u32 =
felt.as_canonical_u64().try_into().map_err(|err: TryFromIntError| {
DeserializationError::InvalidValue(err.to_string())
})?;
data.extend(element.to_le_bytes())
}
data.truncate(len as usize);
let mast = MastForest::read_from_bytes(&data)?;
let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?;
Ok(NoteScript::from_parts(Arc::new(mast), entrypoint))
}
}
impl TryFrom<Vec<Felt>> for NoteScript {
type Error = DeserializationError;
fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
value.as_slice().try_into()
}
}
impl Serializable for NoteScript {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.mast.write_into(target);
target.write_u32(u32::from(self.entrypoint));
}
fn get_size_hint(&self) -> usize {
let mast_size = self.mast.to_bytes().len();
let u32_size = 0u32.get_size_hint();
mast_size + u32_size
}
}
impl Deserializable for NoteScript {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let mast = MastForest::read_from(source)?;
let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
Ok(Self::from_parts(Arc::new(mast), entrypoint))
}
}
impl PrettyPrint for NoteScript {
fn render(&self) -> miden_core::prettier::Document {
use miden_core::prettier::*;
let entrypoint = self.mast[self.entrypoint].to_pretty_print(&self.mast);
indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end")
}
}
impl Display for NoteScript {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.pretty_print(f)
}
}
#[cfg(test)]
mod tests {
use super::{Felt, NoteScript, Vec};
use crate::testing::assembler::assemble_test_library;
use crate::testing::note::DEFAULT_NOTE_SCRIPT;
#[test]
fn test_note_script_to_from_felt() {
let script_src = DEFAULT_NOTE_SCRIPT;
let library =
assemble_test_library("test-note-script-roundtrip", "test::note_roundtrip", script_src);
let note_script = NoteScript::from_library(&library).unwrap();
let encoded: Vec<Felt> = (¬e_script).into();
let decoded: NoteScript = encoded.try_into().unwrap();
assert_eq!(note_script, decoded);
}
#[test]
fn test_note_script_preserves_package_debug_info() {
let library = assemble_test_library(
"test-note-script-debug-info",
"test::note_debug_info",
DEFAULT_NOTE_SCRIPT,
);
let note_script = NoteScript::from_library(&library).unwrap();
assert!(note_script.loaded_mast_forest().package_debug_info().unwrap().is_some());
}
#[test]
fn test_note_script_with_advice_map() {
use miden_core::advice::AdviceMap;
use crate::Word;
let library = assemble_test_library(
"test-note-script-with-advice-map",
"test::note_with_advice_map",
DEFAULT_NOTE_SCRIPT,
);
let script = NoteScript::from_library(&library).unwrap();
assert!(script.mast().advice_map().is_empty());
let original_root = script.root();
let script = script.with_advice_map(AdviceMap::default());
assert_eq!(original_root, script.root());
let key = Word::from([5u32, 6, 7, 8]);
let value = vec![Felt::new_unchecked(100)];
let mut advice_map = AdviceMap::default();
advice_map.insert(key, value.clone());
let script = script.with_advice_map(advice_map);
let mast = script.mast();
let stored = mast.advice_map().get(&key).expect("entry should be present");
assert_eq!(stored.as_ref(), value.as_slice());
}
}