Skip to content

Commit

Permalink
Merge branch 'main' into dev-secp-support
Browse files Browse the repository at this point in the history
  • Loading branch information
whichqua committed Aug 6, 2024
2 parents 8839689 + b68166c commit fa0b878
Show file tree
Hide file tree
Showing 52 changed files with 887 additions and 451 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @maciejka @odesenfans @notlesh
* @odesenfans @notlesh
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: |
mkdir -p build
cairo-compile cairo-lang/src/starkware/starknet/core/os/os.cairo --output build/os_latest.json --cairo_path cairo-lang/src
- run: cargo clippy --all -- -D warnings
- run: cargo clippy --all --all-targets -- -D warnings

tests:
runs-on: ubuntu-latest
Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"crates/bin/hint_tool",
"crates/cairo-type-derive",
"crates/starknet-os",
"crates/starknet-os-types",
"tests",
]

Expand All @@ -30,7 +31,7 @@ cairo-lang-starknet = { version = "2.6.3" }
cairo-lang-starknet-classes = { version = "2.6.3" }
cairo-lang-casm = { version = "2.6.3" }
cairo-type-derive = { version = "0.1.0", path = "crates/cairo-type-derive" }
cairo-vm = { version = "1.0.0-rc5", features = ["extensive_hints", "cairo-1-hints"] }
cairo-vm = { version = "=1.0.0-rc5", features = ["extensive_hints", "cairo-1-hints"] }
clap = { version = "4.5.4", features = ["derive"] }
env_logger = "0.11.3"
futures = "0.3.30"
Expand All @@ -39,6 +40,7 @@ heck = "0.4.1"
hex = "0.4.3"
indexmap = "2.2.6"
indoc = "2"
keccak = "0.1.3"
lazy_static = "1.4.0"
log = "0.4.19"
num-bigint = "0.4"
Expand All @@ -54,6 +56,7 @@ serde_with = "3.3.0"
serde_yaml = "0.9.25"
starknet_api = { version = "=0.10", features = ["testing"] }
starknet-crypto = "0.6.2"
starknet-types-core = "0.1.5"
thiserror = "1.0.48"
tokio = { version = "1.37.0", features = ["rt-multi-thread"] }
uuid = { version = "1.4.0", features = ["v4", "serde"] }
Expand Down
16 changes: 16 additions & 0 deletions crates/starknet-os-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "starknet-os-types"
version.workspace = true
edition.workspace = true
repository.workspace = true
license-file.workspace = true

[dependencies]
blockifier = { workspace = true }
cairo-lang-starknet-classes = { workspace = true }
num-bigint = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
starknet_api = { workspace = true }
starknet-types-core = { workspace = true }
thiserror = { workspace = true }
218 changes: 218 additions & 0 deletions crates/starknet-os-types/src/contract_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use std::cell::OnceCell;
use std::rc::Rc;

use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::hash::GenericClassHash;

pub type CairoLangCasmClass = cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
pub type BlockifierCasmClass = blockifier::execution::contract_class::ContractClassV1;

#[derive(thiserror::Error, Debug)]
pub enum ContractClassError {
#[error("Internal error: no type conversion is possible to generate the desired effect.")]
NoPossibleConversion,

#[error("Could not build Blockifier contract class")]
BlockifierConversionError,

#[error(transparent)]
SerdeError(#[from] serde_json::Error),
}

/// A generic contract class that supports conversion to/from the most commonly used
/// contract class types in Starknet and provides utility methods.
/// Operations are implemented as lazily as possible, i.e. we only convert
/// between different types if strictly necessary.
/// Fields are boxed in an RC for cheap cloning.
#[derive(Debug, Clone)]
pub struct GenericCasmContractClass {
blockifier_contract_class: OnceCell<Rc<BlockifierCasmClass>>,
cairo_lang_contract_class: OnceCell<Rc<CairoLangCasmClass>>,
serialized_class: OnceCell<Rc<Vec<u8>>>,
class_hash: OnceCell<GenericClassHash>,
}

fn blockifier_contract_class_from_cairo_lang_class(
cairo_lang_class: CairoLangCasmClass,
) -> Result<BlockifierCasmClass, ContractClassError> {
let blockifier_class: BlockifierCasmClass =
cairo_lang_class.try_into().map_err(|_| ContractClassError::BlockifierConversionError)?;
Ok(blockifier_class)
}

fn cairo_lang_contract_class_from_bytes(bytes: &[u8]) -> Result<CairoLangCasmClass, ContractClassError> {
let contract_class = serde_json::from_slice(bytes)?;
Ok(contract_class)
}

impl GenericCasmContractClass {
pub fn from_bytes(serialized_class: Vec<u8>) -> Self {
Self {
blockifier_contract_class: OnceCell::new(),
cairo_lang_contract_class: OnceCell::new(),
serialized_class: OnceCell::from(Rc::new(serialized_class)),
class_hash: OnceCell::new(),
}
}

fn build_cairo_lang_class(&self) -> Result<CairoLangCasmClass, ContractClassError> {
if let Some(serialized_class) = self.serialized_class.get() {
let contract_class = serde_json::from_slice(serialized_class)?;
return Ok(contract_class);
}

Err(ContractClassError::NoPossibleConversion)
}

fn build_blockifier_class(&self) -> Result<BlockifierCasmClass, ContractClassError> {
if let Some(cairo_lang_class) = self.cairo_lang_contract_class.get() {
return blockifier_contract_class_from_cairo_lang_class(cairo_lang_class.as_ref().clone());
}

if let Some(serialized_class) = &self.serialized_class.get() {
let cairo_lang_class = cairo_lang_contract_class_from_bytes(serialized_class)?;
self.cairo_lang_contract_class
.set(Rc::new(cairo_lang_class.clone()))
.expect("cairo-lang class is already set");
return blockifier_contract_class_from_cairo_lang_class(cairo_lang_class);
}

Err(ContractClassError::NoPossibleConversion)
}
pub fn get_cairo_lang_contract_class(&self) -> Result<&CairoLangCasmClass, ContractClassError> {
self.cairo_lang_contract_class
.get_or_try_init(|| self.build_cairo_lang_class().map(Rc::new))
.map(|boxed| boxed.as_ref())
}

pub fn get_blockifier_contract_class(&self) -> Result<&BlockifierCasmClass, ContractClassError> {
self.blockifier_contract_class
.get_or_try_init(|| self.build_blockifier_class().map(Rc::new))
.map(|boxed| boxed.as_ref())
}

pub fn to_cairo_lang_contract_class(self) -> Result<CairoLangCasmClass, ContractClassError> {
let cairo_lang_class = self.get_cairo_lang_contract_class()?;
Ok(cairo_lang_class.clone())
}

pub fn to_blockifier_contract_class(self) -> Result<BlockifierCasmClass, ContractClassError> {
let blockifier_class = self.get_blockifier_contract_class()?;
Ok(blockifier_class.clone())
}

fn compute_class_hash(&self) -> Result<GenericClassHash, ContractClassError> {
let compiled_class = self.get_cairo_lang_contract_class()?;
let class_hash_felt = compiled_class.compiled_class_hash();

Ok(GenericClassHash::from_bytes_be(class_hash_felt.to_be_bytes()))
}

pub fn class_hash(&self) -> Result<GenericClassHash, ContractClassError> {
self.class_hash.get_or_try_init(|| self.compute_class_hash()).copied()
}
}

impl Serialize for GenericCasmContractClass {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// It seems like there is no way to just pass the `serialized_class` field as the output
// of `serialize()`, so we are forced to serialize an actual class instance.
let cairo_lang_class =
self.get_cairo_lang_contract_class().map_err(|e| serde::ser::Error::custom(e.to_string()))?;
cairo_lang_class.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for GenericCasmContractClass {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let cairo_lang_class = CairoLangCasmClass::deserialize(deserializer)?;
Ok(Self::from(cairo_lang_class))
}
}

impl From<CairoLangCasmClass> for GenericCasmContractClass {
fn from(cairo_lang_class: CairoLangCasmClass) -> Self {
Self {
blockifier_contract_class: Default::default(),
cairo_lang_contract_class: OnceCell::from(Rc::new(cairo_lang_class)),
serialized_class: Default::default(),
class_hash: Default::default(),
}
}
}

impl From<BlockifierCasmClass> for GenericCasmContractClass {
fn from(blockifier_class: BlockifierCasmClass) -> Self {
Self {
blockifier_contract_class: OnceCell::from(Rc::new(blockifier_class)),
cairo_lang_contract_class: Default::default(),
serialized_class: Default::default(),
class_hash: Default::default(),
}
}
}

impl TryFrom<GenericCasmContractClass> for BlockifierCasmClass {
type Error = ContractClassError;

fn try_from(contract_class: GenericCasmContractClass) -> Result<Self, Self::Error> {
contract_class.to_blockifier_contract_class()
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use starknet_types_core::felt::Felt;

use super::*;

const CONTRACT_BYTES: &[u8] = include_bytes!(
"../../../tests/integration/contracts/blockifier_contracts/feature_contracts/cairo1/compiled/test_contract.\
casm.json"
);

#[test]
fn test_serialize_and_deserialize() {
let generic_class = GenericCasmContractClass::from_bytes(CONTRACT_BYTES.to_vec());

let serialized_class = serde_json::to_vec(&generic_class).unwrap();
// Check that the deserialization works
let _deserialized_class: GenericCasmContractClass = serde_json::from_slice(&serialized_class).unwrap();
}

#[test]
fn test_compare_serde_formats() {
let generic_class = GenericCasmContractClass::from_bytes(CONTRACT_BYTES.to_vec());
let cairo_lang_class: CairoLangCasmClass = serde_json::from_slice(CONTRACT_BYTES).unwrap();

let generated_cairo_lang_class = generic_class.to_cairo_lang_contract_class().unwrap();

assert_eq!(generated_cairo_lang_class, cairo_lang_class);

let deserialized_generic_class: GenericCasmContractClass = serde_json::from_slice(CONTRACT_BYTES).unwrap();
let deserialized_cairo_lang_class = deserialized_generic_class.to_cairo_lang_contract_class().unwrap();

assert_eq!(deserialized_cairo_lang_class, cairo_lang_class);
}

#[test]
fn test_class_hash() {
let generic_class = GenericCasmContractClass::from_bytes(CONTRACT_BYTES.to_vec());
let class_hash = generic_class.class_hash().unwrap();

// Some weird type conversions here to load the class hash from string easily, may be
// improved with more methods on `Hash` / `GenericClassHash`.
let expected_class_hash =
Felt::from_str("0x607c67298d45092cca5b2ae6804373dd8a2cbe7d2ec4072b3f67097461d5ff4").unwrap();
assert_eq!(Felt::from(*class_hash), expected_class_hash);
}
}
Loading

0 comments on commit fa0b878

Please sign in to comment.