Skip to content

Commit

Permalink
Fix: require std to build CairoPie from ZIP archive
Browse files Browse the repository at this point in the history
  • Loading branch information
odesenfans committed Jan 17, 2024
1 parent d29036e commit 93d7bbc
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 112 deletions.
2 changes: 2 additions & 0 deletions vm/src/serde/deserialize_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ pub struct HintLocation {
pub n_prefix_newlines: u32,
}

#[cfg(feature = "std")]
pub(crate) fn biguint_from_number(n: Number) -> Option<BigUint> {
BigUint::parse_bytes(n.to_string().as_bytes(), 10)
}
Expand All @@ -245,6 +246,7 @@ pub(crate) fn felt_from_number(n: Number) -> Option<Felt252> {
}
}

#[cfg(feature = "std")]
pub(crate) fn deserialize_biguint_from_number<'de, D>(deserializer: D) -> Result<BigUint, D::Error>
where
D: Deserializer<'de>,
Expand Down
1 change: 1 addition & 0 deletions vm/src/types/errors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[cfg(feature = "std")]
pub mod cairo_pie_error;
pub mod math_errors;
pub mod program_errors;
240 changes: 128 additions & 112 deletions vm/src/vm/runners/cairo_pie.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use num_bigint::BigUint;
use num_integer::Integer;
use std::io::{Read, Seek};
#[cfg(feature = "std")]
use std::path::Path;
use {
crate::types::errors::cairo_pie_error::{CairoPieError, DeserializeMemoryError},
num_integer::Integer,
serde::de::DeserializeOwned,
std::io::{Read, Seek},
std::path::Path,
zip::read::ZipFile,
};

use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use zip::read::ZipFile;

use super::cairo_runner::ExecutionResources;
use crate::serde::deserialize_program::deserialize_biguint_from_number;
use crate::types::errors::cairo_pie_error::{CairoPieError, DeserializeMemoryError};
use crate::{
serde::deserialize_program::BuiltinName,
stdlib::{collections::HashMap, prelude::*},
Expand Down Expand Up @@ -77,84 +78,85 @@ pub struct CairoPie {
pub version: CairoPieVersion,
}

fn parse_zip_file<T: DeserializeOwned>(mut zip_file: ZipFile) -> Result<T, CairoPieError> {
let mut buf = vec![];
zip_file.read_to_end(&mut buf)?;
serde_json::from_slice(&buf).map_err(|e| e.into())
}

const N_SEGMENT_BITS: usize = 16;
const N_OFFSET_BITS: usize = 47;
const SEGMENT_MASK: u64 = ((1 << N_SEGMENT_BITS) - 1) << N_OFFSET_BITS;
const OFFSET_MASK: u64 = (1 << N_OFFSET_BITS) - 1;

fn maybe_relocatable_from_le_bytes(bytes: &[u8]) -> MaybeRelocatable {
// Little-endian -> the relocatable bit is in the last element
let is_relocatable = (bytes[bytes.len() - 1] & 0x80) != 0;

if !is_relocatable {
let felt = Felt252::from_bytes_le_slice(bytes);
return MaybeRelocatable::Int(felt);
#[cfg(feature = "std")]
impl CairoPie {
const N_SEGMENT_BITS: usize = 16;
const N_OFFSET_BITS: usize = 47;
const SEGMENT_MASK: u64 = ((1 << Self::N_SEGMENT_BITS) - 1) << Self::N_OFFSET_BITS;
const OFFSET_MASK: u64 = (1 << Self::N_OFFSET_BITS) - 1;

fn parse_zip_file<T: DeserializeOwned>(mut zip_file: ZipFile) -> Result<T, CairoPieError> {
let mut buf = vec![];
zip_file.read_to_end(&mut buf)?;
serde_json::from_slice(&buf).map_err(|e| e.into())
}

// Relocatable values are guaranteed to fit in a u64
let value = {
let mut value = 0;
for (index, byte) in bytes[..8].iter().enumerate() {
value += u64::from(*byte) << (index * 8);
fn maybe_relocatable_from_le_bytes(bytes: &[u8]) -> MaybeRelocatable {
// Little-endian -> the relocatable bit is in the last element
let is_relocatable = (bytes[bytes.len() - 1] & 0x80) != 0;

if !is_relocatable {
let felt = Felt252::from_bytes_le_slice(bytes);
return MaybeRelocatable::Int(felt);
}
value
};

let segment = (value & SEGMENT_MASK) >> N_OFFSET_BITS;
let offset = value & OFFSET_MASK;
MaybeRelocatable::RelocatableValue(Relocatable::from((segment as isize, offset as usize)))
}
// Relocatable values are guaranteed to fit in a u64
let value = {
let mut value = 0;
for (index, byte) in bytes[..8].iter().enumerate() {
value += u64::from(*byte) << (index * 8);
}
value
};

fn read_memory_file<R: Read>(
mut reader: R,
addr_size: usize,
felt_size: usize,
) -> Result<CairoPieMemory, DeserializeMemoryError> {
let memory_cell_size = addr_size + felt_size;
let mut memory = CairoPieMemory::new();
let mut pos: usize = 0;

loop {
let mut element = vec![0; memory_cell_size];
match reader.read(&mut element) {
Ok(n) => {
if n == 0 {
break;
}
if n != memory_cell_size {
return Err(DeserializeMemoryError::UnexpectedEof);
let segment = (value & Self::SEGMENT_MASK) >> Self::N_OFFSET_BITS;
let offset = value & Self::OFFSET_MASK;
MaybeRelocatable::RelocatableValue(Relocatable::from((segment as isize, offset as usize)))
}

fn read_memory_file<R: Read>(
mut reader: R,
addr_size: usize,
felt_size: usize,
) -> Result<CairoPieMemory, DeserializeMemoryError> {
let memory_cell_size = addr_size + felt_size;
let mut memory = CairoPieMemory::new();
let mut pos: usize = 0;

loop {
let mut element = vec![0; memory_cell_size];
match reader.read(&mut element) {
Ok(n) => {
if n == 0 {
break;
}
if n != memory_cell_size {
return Err(DeserializeMemoryError::UnexpectedEof);
}
}
Err(e) => return Err(e.into()),
}
Err(e) => return Err(e.into()),
}
let (address_bytes, value_bytes) = element.split_at(addr_size);
let address = maybe_relocatable_from_le_bytes(address_bytes.clone());
let value = maybe_relocatable_from_le_bytes(value_bytes);

match address {
MaybeRelocatable::RelocatableValue(relocatable) => {
memory.push((
(relocatable.segment_index as usize, relocatable.offset),
value,
));
}
MaybeRelocatable::Int(_value) => {
return Err(DeserializeMemoryError::AddressIsNotRelocatable(pos));
let (address_bytes, value_bytes) = element.split_at(addr_size);
let address = Self::maybe_relocatable_from_le_bytes(address_bytes.clone());
let value = Self::maybe_relocatable_from_le_bytes(value_bytes);

match address {
MaybeRelocatable::RelocatableValue(relocatable) => {
memory.push((
(relocatable.segment_index as usize, relocatable.offset),
value,
));
}
MaybeRelocatable::Int(_value) => {
return Err(DeserializeMemoryError::AddressIsNotRelocatable(pos));
}
}
pos += memory_cell_size;
}
pos += memory_cell_size;
}

Ok(memory)
}
Ok(memory)
}

impl CairoPie {
/// Builds a CairoPie object from the Python VM ZIP archive format.
///
/// This function expects the ZIP archive to contain the following files:
Expand All @@ -170,12 +172,12 @@ impl CairoPie {
pub fn from_zip_archive<R: Read + Seek>(
mut zip: zip::ZipArchive<R>,
) -> Result<CairoPie, CairoPieError> {
let metadata: CairoPieMetadata = parse_zip_file(zip.by_name("metadata.json")?)?;
let metadata: CairoPieMetadata = Self::parse_zip_file(zip.by_name("metadata.json")?)?;
let execution_resources: ExecutionResources =
parse_zip_file(zip.by_name("execution_resources.json")?)?;
Self::parse_zip_file(zip.by_name("execution_resources.json")?)?;
let additional_data: HashMap<String, BuiltinAdditionalData> =
parse_zip_file(zip.by_name("additional_data.json")?)?;
let version: CairoPieVersion = parse_zip_file(zip.by_name("version.json")?)?;
Self::parse_zip_file(zip.by_name("additional_data.json")?)?;
let version: CairoPieVersion = Self::parse_zip_file(zip.by_name("version.json")?)?;

let addr_size: usize = 8;
let felt_bytes = {
Expand All @@ -185,7 +187,7 @@ impl CairoPie {
}
n_bytes as usize
};
let memory = read_memory_file(zip.by_name("memory.bin")?, addr_size, felt_bytes)?;
let memory = Self::read_memory_file(zip.by_name("memory.bin")?, addr_size, felt_bytes)?;

Ok(CairoPie {
metadata,
Expand All @@ -206,7 +208,8 @@ impl CairoPie {
}
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Deserialize))]
pub struct CairoPieMetadata {
pub program: StrippedProgram,
pub program_segment: SegmentInfo,
Expand All @@ -217,15 +220,24 @@ pub struct CairoPieMetadata {
pub extra_segments: Vec<SegmentInfo>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[derive(Serialize, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Deserialize))]
pub struct StrippedProgram {
#[serde(serialize_with = "serde_impl::serialize_program_data")]
#[serde(deserialize_with = "serde_impl::deserialize_array_of_felts")]
#[cfg_attr(
feature = "std",
serde(deserialize_with = "serde_impl::de::deserialize_array_of_felts")
)]
pub data: Vec<MaybeRelocatable>,
pub builtins: Vec<BuiltinName>,
pub main: usize,
#[serde(deserialize_with = "deserialize_biguint_from_number")]
#[serde(serialize_with = "serde_impl::serialize_prime")]
#[cfg_attr(
feature = "std",
serde(
deserialize_with = "crate::serde::deserialize_program::deserialize_biguint_from_number"
)
)]
pub prime: BigUint,
}

Expand All @@ -235,14 +247,11 @@ pub struct CairoPieVersion {
}

mod serde_impl {
use crate::serde::deserialize_program::felt_from_number;
use crate::{types::relocatable::MaybeRelocatable, Felt252};
use num_bigint::BigUint;
use num_traits::Num;
use serde::de::SeqAccess;
use serde::{de, ser::SerializeSeq, Deserializer, Serialize, Serializer};
use serde::{ser::SerializeSeq, Serialize, Serializer};
use serde_json::Number;
use std::fmt;

use crate::utils::CAIRO_PRIME;

Expand Down Expand Up @@ -353,35 +362,42 @@ mod serde_impl {
Number::from_string_unchecked(CAIRO_PRIME.to_string()).serialize(serializer)
}

pub(crate) struct MaybeRelocatableNumberVisitor;
#[cfg(feature = "std")]

impl<'de> de::Visitor<'de> for MaybeRelocatableNumberVisitor {
type Value = Vec<MaybeRelocatable>;
pub mod de {
use crate::serde::deserialize_program::felt_from_number;
use crate::vm::runners::cairo_pie::MaybeRelocatable;
use serde_json::Number;
pub(crate) struct MaybeRelocatableNumberVisitor;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("Could not deserialize array of hexadecimal")
}
impl<'de> serde::de::Visitor<'de> for MaybeRelocatableNumberVisitor {
type Value = Vec<MaybeRelocatable>;

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut data: Vec<MaybeRelocatable> = vec![];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("Could not deserialize array of hexadecimal")
}

while let Some(n) = seq.next_element::<Number>()? {
let felt = felt_from_number(n.clone()).ok_or(de::Error::custom(format!(
"Failed to parse number as felt: {n}"
)))?;
data.push(MaybeRelocatable::Int(felt));
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut data: Vec<MaybeRelocatable> = vec![];

while let Some(n) = seq.next_element::<Number>()? {
let felt = felt_from_number(n.clone()).ok_or(serde::de::Error::custom(
format!("Failed to parse number as felt: {n}"),
))?;
data.push(MaybeRelocatable::Int(felt));
}
Ok(data)
}
Ok(data)
}
}

pub fn deserialize_array_of_felts<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Vec<MaybeRelocatable>, D::Error> {
d.deserialize_seq(MaybeRelocatableNumberVisitor)
pub fn deserialize_array_of_felts<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<Vec<MaybeRelocatable>, D::Error> {
d.deserialize_seq(MaybeRelocatableNumberVisitor)
}
}
}

Expand Down Expand Up @@ -470,7 +486,7 @@ mod test {
#[case] expected_offset: usize,
) {
let bytes: [u8; 8] = value.to_le_bytes();
let maybe_relocatable = maybe_relocatable_from_le_bytes(&bytes);
let maybe_relocatable = CairoPie::maybe_relocatable_from_le_bytes(&bytes);

assert_eq!(
maybe_relocatable,
Expand All @@ -485,7 +501,7 @@ mod test {
#[case([0, 0, 0, 0, 0, 0, 0], 0)]
#[case([0, 1, 2, 3, 4, 5, 6], 0x6050403020100)]
fn test_memory_deserialize_integer(#[case] bytes: [u8; 7], #[case] expected_value: u64) {
let maybe_relocatable = maybe_relocatable_from_le_bytes(&bytes);
let maybe_relocatable = CairoPie::maybe_relocatable_from_le_bytes(&bytes);

assert_eq!(
maybe_relocatable,
Expand All @@ -498,7 +514,7 @@ mod test {
let path = Path::new("../cairo_programs/manually_compiled/fibonacci_cairo_pie/memory.bin");
let file = File::open(path).unwrap();

let memory = read_memory_file(file, 8, 32).expect("Could not read memory file");
let memory = CairoPie::read_memory_file(file, 8, 32).expect("Could not read memory file");
assert_eq!(memory.len(), 88);
}

Expand Down

0 comments on commit 93d7bbc

Please sign in to comment.