Skip to content

Commit

Permalink
Feature: bump cairo lang to 0.13.2.1 (#367)
Browse files Browse the repository at this point in the history
* Chore: bump cairo-lang to 0.13.2.1

* Feature: bump to cairo-lang 0.13.2.1

Problem: we want to support the latest version of the OS.

Solution: bump the cairo-lang submodule and implement the missing hints!

* itests ok

* clippy

* done

* fix unimplemented.rs

* rebase fix: kzg tests

---------

Co-authored-by: Herman Obst Demaestri <hodemaestri@gmail.com>
  • Loading branch information
odesenfans and HermanObst committed Sep 19, 2024
1 parent da993e6 commit 287dfd2
Show file tree
Hide file tree
Showing 26 changed files with 364 additions and 66 deletions.
6 changes: 6 additions & 0 deletions crates/bin/prove_block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ pub async fn prove_block(

let compiled_classes = processed_state_update.compiled_classes;
let deprecated_compiled_classes = processed_state_update.deprecated_compiled_classes;
let declared_class_hash_component_hashes: HashMap<_, _> = processed_state_update
.declared_class_hash_component_hashes
.into_iter()
.map(|(class_hash, component_hashes)| (class_hash, component_hashes.to_vec()))
.collect();

// query storage proofs for each accessed contract
let class_hashes: Vec<&Felt252> = class_hash_to_compiled_class_hash.keys().collect();
Expand Down Expand Up @@ -307,6 +312,7 @@ pub async fn prove_block(
class_hash_to_compiled_class_hash,
general_config,
transactions,
declared_class_hash_to_component_hashes: declared_class_hash_component_hashes,
new_block_hash: block_with_txs.block_hash,
prev_block_hash: previous_block.block_hash,
full_output: false,
Expand Down
59 changes: 37 additions & 22 deletions crates/bin/prove_block/src/state_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rpc_client::RpcClient;
use starknet::core::types::{BlockId, MaybePendingStateUpdate, StarknetError, StateDiff, TransactionTraceWithHash};
use starknet::providers::{Provider, ProviderError};
use starknet_os_types::casm_contract_class::GenericCasmContractClass;
use starknet_os_types::class_hash_utils::ContractClassComponentHashes;
use starknet_os_types::compiled_class::GenericCompiledClass;
use starknet_os_types::deprecated_compiled_class::GenericDeprecatedCompiledClass;
use starknet_os_types::sierra_contract_class::GenericSierraContractClass;
Expand All @@ -19,6 +20,7 @@ pub struct FormattedStateUpdate {
pub class_hash_to_compiled_class_hash: HashMap<Felt252, Felt252>,
pub compiled_classes: HashMap<Felt252, GenericCasmContractClass>,
pub deprecated_compiled_classes: HashMap<Felt252, GenericDeprecatedCompiledClass>,
pub declared_class_hash_component_hashes: HashMap<Felt252, ContractClassComponentHashes>,
}

/// Given the `block_id` of the target block to prove, it:
Expand Down Expand Up @@ -46,14 +48,18 @@ pub(crate) async fn get_formatted_state_update(
rpc_client.starknet_rpc().trace_block_transactions(block_id).await.expect("Failed to get block tx traces");
let (accessed_addresses, accessed_classes) = get_subcalled_contracts_from_tx_traces(&traces);

let declared_classes: HashSet<_> =
state_diff.declared_classes.iter().map(|declared_item| declared_item.class_hash).collect();

// TODO: Handle deprecated classes
let mut class_hash_to_compiled_class_hash: HashMap<Felt252, Felt252> = format_declared_classes(&state_diff);
let (compiled_contract_classes, deprecated_compiled_contract_classes) =
let (compiled_contract_classes, deprecated_compiled_contract_classes, declared_class_hash_component_hashes) =
build_compiled_class_and_maybe_update_class_hash_to_compiled_class_hash(
rpc_client,
previous_block_id,
block_id,
&accessed_addresses,
&declared_classes,
&accessed_classes,
&mut class_hash_to_compiled_class_hash,
)
Expand All @@ -64,20 +70,17 @@ pub(crate) async fn get_formatted_state_update(
class_hash_to_compiled_class_hash,
compiled_classes: compiled_contract_classes,
deprecated_compiled_classes: deprecated_compiled_contract_classes,
declared_class_hash_component_hashes,
},
traces,
))
}

/// Retrieves the compiled class for the given class hash at a specific block
/// by getting the class from the RPC and compiling it to CASM if necessary (Cairo 1).
async fn get_compiled_class_for_class_hash(
provider: &RpcClient,
block_id: BlockId,
class_hash: Felt252,
fn compile_contract_class(
contract_class: starknet::core::types::ContractClass,
) -> Result<GenericCompiledClass, ProveBlockError> {
let contract_class = provider.starknet_rpc().get_class(block_id, class_hash).await?;

let compiled_class = match contract_class {
starknet::core::types::ContractClass::Sierra(flattened_sierra_cc) => {
let sierra_class = GenericSierraContractClass::from(flattened_sierra_cc);
Expand All @@ -104,23 +107,22 @@ async fn add_compiled_class_from_contract_to_os_input(
deprecated_compiled_contract_classes: &mut HashMap<Felt, GenericDeprecatedCompiledClass>,
) -> Result<(), ProveBlockError> {
let class_hash = rpc_client.starknet_rpc().get_class_hash_at(block_id, contract_address).await?;
add_compiled_class_from_class_hash_to_os_input(
rpc_client,
let contract_class = rpc_client.starknet_rpc().get_class(block_id, class_hash).await?;

add_compiled_class_to_os_input(
class_hash,
block_id,
contract_class,
class_hash_to_compiled_class_hash,
compiled_contract_classes,
deprecated_compiled_contract_classes,
)
.await
}

/// Fetches (+ compile) the contract class for the specified class at the specified block
/// and adds it to the hashmaps that will then be added to the OS input.
async fn add_compiled_class_from_class_hash_to_os_input(
provider: &RpcClient,
fn add_compiled_class_to_os_input(
class_hash: Felt,
block_id: BlockId,
contract_class: starknet::core::types::ContractClass,
class_hash_to_compiled_class_hash: &mut HashMap<Felt252, Felt252>,
compiled_contract_classes: &mut HashMap<Felt, GenericCasmContractClass>,
deprecated_compiled_contract_classes: &mut HashMap<Felt, GenericDeprecatedCompiledClass>,
Expand All @@ -130,7 +132,7 @@ async fn add_compiled_class_from_class_hash_to_os_input(
return Ok(());
}

let compiled_class = get_compiled_class_for_class_hash(provider, block_id, class_hash).await?;
let compiled_class = compile_contract_class(contract_class)?;
let compiled_class_hash = compiled_class.class_hash()?;

class_hash_to_compiled_class_hash.insert(class_hash, compiled_class_hash.into());
Expand Down Expand Up @@ -159,10 +161,15 @@ async fn build_compiled_class_and_maybe_update_class_hash_to_compiled_class_hash
previous_block_id: BlockId,
block_id: BlockId,
accessed_addresses: &HashSet<Felt252>,
declared_classes: &HashSet<Felt252>,
accessed_classes: &HashSet<Felt252>,
class_hash_to_compiled_class_hash: &mut HashMap<Felt252, Felt252>,
) -> Result<
(HashMap<Felt252, GenericCasmContractClass>, HashMap<Felt252, GenericDeprecatedCompiledClass>),
(
HashMap<Felt252, GenericCasmContractClass>,
HashMap<Felt252, GenericDeprecatedCompiledClass>,
HashMap<Felt252, ContractClassComponentHashes>,
),
ProveBlockError,
> {
let mut compiled_contract_classes: HashMap<Felt252, GenericCasmContractClass> = HashMap::new();
Expand Down Expand Up @@ -203,18 +210,26 @@ async fn build_compiled_class_and_maybe_update_class_hash_to_compiled_class_hash
}

for class_hash in accessed_classes {
add_compiled_class_from_class_hash_to_os_input(
provider,
let contract_class = provider.starknet_rpc().get_class(block_id, class_hash).await?;
add_compiled_class_to_os_input(
*class_hash,
previous_block_id,
contract_class,
class_hash_to_compiled_class_hash,
&mut compiled_contract_classes,
&mut deprecated_compiled_contract_classes,
)
.await?;
)?;
}

let mut declared_class_hash_to_component_hashes = HashMap::new();
for class_hash in declared_classes {
let contract_class = provider.starknet_rpc().get_class(block_id, class_hash).await?;
if let starknet::core::types::ContractClass::Sierra(flattened_sierra_class) = &contract_class {
let component_hashes = ContractClassComponentHashes::from(flattened_sierra_class.clone());
declared_class_hash_to_component_hashes.insert(*class_hash, component_hashes);
}
}

Ok((compiled_contract_classes, deprecated_compiled_contract_classes))
Ok((compiled_contract_classes, deprecated_compiled_contract_classes, declared_class_hash_to_component_hashes))
}

/// Format StateDiff's DeclaredClassItem to a HashMap<class_hash, compiled_class_hash>
Expand Down
3 changes: 2 additions & 1 deletion crates/starknet-os-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
starknet_api = { workspace = true }
starknet-types-core = { workspace = true }
starknet-core = { workspace = true }
starknet-crypto = { workspace = true }
starknet-types-core = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }

Expand Down
136 changes: 136 additions & 0 deletions crates/starknet-os-types/src/class_hash_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use starknet_core::types::SierraEntryPoint;
use starknet_core::utils::starknet_keccak;
use starknet_crypto::{poseidon_hash_many, FieldElement};
use starknet_types_core::felt::Felt;

const CLASS_VERSION_PREFIX: &str = "CONTRACT_CLASS_V";

/// A convenience function that computes a Poseidon hash over Felts rather than starknet-crypto
/// FieldElement types. There is no `From` implementation between these types so this function
/// masks some ugly byte mashing.
fn poseidon_hash_many_felts<FeltIter: Iterator<Item = Felt>>(felts: FeltIter) -> Felt {
let field_elements: Vec<_> = felts.map(|x| FieldElement::from_bytes_be(&x.to_bytes_be()).unwrap()).collect();
let hash = poseidon_hash_many(&field_elements);

Felt::from_bytes_be(&hash.to_bytes_be())
}

/// Computes hash on a list of given entry points (starknet-core types).
fn compute_hash_on_sierra_entry_points<'a, EntryPoints: Iterator<Item = &'a SierraEntryPoint>>(
entry_points: EntryPoints,
) -> Felt {
let flat_entry_points =
entry_points.flat_map(|entry_point| [entry_point.selector, Felt::from(entry_point.function_idx)]);

poseidon_hash_many_felts(flat_entry_points)
}

fn hash_abi(abi: &str) -> Felt {
starknet_keccak(abi.as_bytes())
}

/// Holds the hashes of the contract class components, to be used for calculating the final hash.
/// Note: the order of the struct member must not be changed since it determines the hash order.
#[derive(Debug, Clone, PartialEq)]
pub struct ContractClassComponentHashes {
contract_class_version: Felt,
external_functions_hash: Felt,
l1_handlers_hash: Felt,
constructors_hash: Felt,
abi_hash: Felt,
sierra_program_hash: Felt,
}

impl ContractClassComponentHashes {
pub fn to_vec(self) -> Vec<Felt> {
vec![
self.contract_class_version,
self.external_functions_hash,
self.l1_handlers_hash,
self.constructors_hash,
self.abi_hash,
self.sierra_program_hash,
]
}
}

impl From<starknet_core::types::FlattenedSierraClass> for ContractClassComponentHashes {
fn from(sierra_class: starknet_core::types::FlattenedSierraClass) -> Self {
let version_str = format!("{CLASS_VERSION_PREFIX}{}", sierra_class.contract_class_version);
let contract_class_version = Felt::from_bytes_be_slice(version_str.as_bytes());

let sierra_program_hash = poseidon_hash_many_felts(sierra_class.sierra_program.into_iter());

Self {
contract_class_version,
external_functions_hash: compute_hash_on_sierra_entry_points(
sierra_class.entry_points_by_type.external.iter(),
),
l1_handlers_hash: compute_hash_on_sierra_entry_points(sierra_class.entry_points_by_type.l1_handler.iter()),
constructors_hash: compute_hash_on_sierra_entry_points(
sierra_class.entry_points_by_type.constructor.iter(),
),
abi_hash: hash_abi(&sierra_class.abi),
sierra_program_hash,
}
}
}

#[cfg(test)]
mod tests {
use rstest::rstest;
use starknet_core::types::contract::SierraClass;

use super::*;

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

const EMPTY_CONTRACT_SIERRA_CLASS: &[u8] = include_bytes!(
"../../../tests/integration/contracts/blockifier_contracts/feature_contracts/cairo1/compiled/empty_contract.\
sierra"
);

/// Tests that component hashing works.
/// The following hashes were generated manually by using the following Python snippet:
/// ```python
/// sierra_path = <path to test_contract.sierra>
/// contract_class = load_sierra(sierra_path)
/// py_compute_class_hash(contract_class)
/// ```
#[rstest]
#[case::test_contract(
TEST_CONTRACT_SIERRA_CLASS,
ContractClassComponentHashes {
contract_class_version: Felt::from_hex_unchecked("0x434f4e54524143545f434c4153535f56302e312e30"),
external_functions_hash: Felt::from_hex_unchecked("0x370b584bc5249817d1e14a4af2313713d26f5ff2a23706cd3a4245f7b941119"),
l1_handlers_hash: Felt::from_hex_unchecked("0x2461f0657352958f4bd24f8bc8e595f9b0282f89d108a3a1c56c7c36a991c51"),
constructors_hash: Felt::from_hex_unchecked("0x33307d0ffeafc7e10284cf096023309a5b7a3c8dec507551518e4d3965a660"),
abi_hash: Felt::from_hex_unchecked("0x3993a88597ef45083b570b7c265cc641dc7dd91795540f0b1eea5c85437c8ac"),
sierra_program_hash: Felt::from_hex_unchecked("0x44dadb1dfc3bd7f8b3cd171a346d7bdaa94dcc3805dee9f538fa0cef60a0072"),
}
)]
#[case::empty_contract(
EMPTY_CONTRACT_SIERRA_CLASS,
ContractClassComponentHashes {
contract_class_version: Felt::from_hex_unchecked("0x434f4e54524143545f434c4153535f56302e312e30"),
external_functions_hash: Felt::from_hex_unchecked("0x2272be0f580fd156823304800919530eaa97430e972d7213ee13f4fbf7a5dbc"),
l1_handlers_hash: Felt::from_hex_unchecked("0x2272be0f580fd156823304800919530eaa97430e972d7213ee13f4fbf7a5dbc"),
constructors_hash: Felt::from_hex_unchecked("0x2272be0f580fd156823304800919530eaa97430e972d7213ee13f4fbf7a5dbc"),
abi_hash: Felt::from_hex_unchecked("0x21a74ba4fe8685313cf0f0c2a7da1284740c54d5009bd312585e6302274e946"),
sierra_program_hash: Felt::from_hex_unchecked("0x49eead8efbb63e415f263237a9e6a010afa0557c69cabee04abbdbcb64c34e8"),
}
)]
fn test_component_hashes_from_sierra_class(
#[case] sierra_class_bytes: &[u8],
#[case] expected_component_hashes: ContractClassComponentHashes,
) {
let sierra_class: SierraClass = serde_json::from_slice(sierra_class_bytes).unwrap();
let flattened_sierra_class = sierra_class.flatten().unwrap();

let component_hashes = ContractClassComponentHashes::from(flattened_sierra_class);
assert_eq!(component_hashes, expected_component_hashes)
}
}
1 change: 1 addition & 0 deletions crates/starknet-os-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod casm_contract_class;
pub mod chain_id;
pub mod class_hash_utils;
pub mod compiled_class;
pub mod deprecated_compiled_class;
pub mod error;
Expand Down
27 changes: 7 additions & 20 deletions crates/starknet-os-types/src/sierra_contract_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::cell::OnceCell;
use std::sync::Arc;

use cairo_vm::Felt252;
use pathfinder_gateway_types::class_hash::compute_class_hash;
use serde::ser::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::serde_as;
Expand Down Expand Up @@ -54,7 +53,10 @@ impl GenericSierraContractClass {

fn build_starknet_core_class(&self) -> Result<StarknetCoreSierraContractClass, ContractClassError> {
let serialized_class = self.get_serialized_contract_class()?;
serde_json::from_slice(serialized_class).map_err(Into::into)
let sierra_class: starknet_core::types::contract::SierraClass =
serde_json::from_slice(serialized_class).map_err(ContractClassError::SerdeError)?;

sierra_class.flatten().map_err(|e| ContractClassError::SerdeError(serde_json::Error::custom(e)))
}
pub fn get_cairo_lang_contract_class(&self) -> Result<&CairoLangSierraContractClass, ContractClassError> {
self.cairo_lang_contract_class
Expand All @@ -79,24 +81,9 @@ impl GenericSierraContractClass {
}

fn compute_class_hash(&self) -> Result<GenericClassHash, ContractClassError> {
// if we have a starknet_core type, we can ask it for a class_hash without any type conversion
return if let Some(sn_core_cc) = self.starknet_core_contract_class.get() {
let class_hash = sn_core_cc.as_ref().class_hash();
Ok(GenericClassHash::new(class_hash.into()))
} else {
// otherwise, we have a cairo_lang contract_class which we can serialize and then
// deserialize via ContractClassForPathfinderCompat
// TODO: improve resilience and performance
let contract_class = self.get_cairo_lang_contract_class()?;
let contract_class_compat = ContractClassForPathfinderCompat::from(contract_class.clone());

let contract_dump =
serde_json::to_vec(&contract_class_compat).expect("JSON serialization failed unexpectedly.");
let computed_class_hash = compute_class_hash(&contract_dump)
.map_err(|e| ContractClassError::HashError(format!("Failed to compute class hash: {}", e)))?;

Ok(GenericClassHash::from_bytes_be(computed_class_hash.hash().0.to_be_bytes()))
};
let starknet_core_contract_class = self.get_starknet_core_contract_class()?;
let class_hash = starknet_core_contract_class.class_hash();
Ok(GenericClassHash::new(class_hash.into()))
}

pub fn class_hash(&self) -> Result<GenericClassHash, ContractClassError> {
Expand Down
2 changes: 1 addition & 1 deletion crates/starknet-os/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ heck = { workspace = true }
hex = { workspace = true }
indexmap = { workspace = true }
indoc = { workspace = true }
keccak = { workspace = true }
keccak = { workspace = true }
lazy_static = { workspace = true }
log = { workspace = true }
num-bigint = { workspace = true }
Expand Down
Loading

0 comments on commit 287dfd2

Please sign in to comment.