Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f9f5f68
added conversion of StarkFelt to web3::types::U256 + unit test
marioiordanov Mar 29, 2023
793df44
Added a struct for starkfelt that serializes to decimal string
marioiordanov Mar 29, 2023
23fd213
added starknet_keccak function
marioiordanov Mar 30, 2023
5e4715b
made serde serde json use arbitrary precision
marioiordanov Mar 30, 2023
f010b31
json formatter for starknet
marioiordanov Mar 30, 2023
f802d31
methods for removing entries from a json obj, when used with serde js…
marioiordanov Mar 30, 2023
90ebeb7
unit tests for json obj modification and convertion
marioiordanov Mar 30, 2023
9362cb8
method for computing contract class hash
marioiordanov Mar 30, 2023
d4281c1
format code
marioiordanov Apr 4, 2023
37a357b
clippy + fmt
marioiordanov Apr 4, 2023
1084ab0
Merge branch 'main' into class_hash
marioiordanov Apr 4, 2023
6425512
clippy after merge
marioiordanov Apr 4, 2023
8bec3e1
add Deserialize trait to StarkFeltAsDecimal
marioiordanov Apr 5, 2023
211f356
Revert "add Deserialize trait to StarkFeltAsDecimal"
marioiordanov Apr 5, 2023
f2d615c
changed cargo dependency format
marioiordanov May 2, 2023
7352917
removed needless println! and unsafe block
marioiordanov May 3, 2023
f8f8cc6
used informative message
marioiordanov May 3, 2023
1f10947
replaced json with JSON
marioiordanov May 3, 2023
46c78f6
remove unused variable when returning from function
marioiordanov May 3, 2023
8edafa0
removed test prefix
marioiordanov May 3, 2023
439eee8
renamed variable in test
marioiordanov May 3, 2023
417faa9
added comment for starknet formatter
marioiordanov May 3, 2023
d65538f
comment test for scientific notation
marioiordanov May 3, 2023
8699891
removed test from serde_utils_test to utils_test
marioiordanov May 3, 2023
df98097
removed unused method
marioiordanov May 3, 2023
8fea9b6
cargo fmt
marioiordanov May 3, 2023
2901303
replace expect with Error
marioiordanov May 8, 2023
ecf4907
cargo clippy
marioiordanov May 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ testing = []
hex = { version = "0.4.3" }
indexmap = { version = "1.9.2", features = ["serde"] }
once_cell = { version = "1.16.0" }
sha3 = { version = "0.10.6" }
serde = { version = "1.0.130", features = ["derive", "rc"] }
serde_json = { version = "1.0.81" }
serde_json = { version = "1.0.81", features = ["preserve_order", "arbitrary_precision"] }
starknet-crypto = { version = "0.2.0" }
thiserror = { version = "1.0.31" }
web3 = { version = "0.18.0" }
Expand Down
4,875 changes: 4,875 additions & 0 deletions resources/contract_compiled.json

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use std::fmt::Debug;
use derive_more::Display;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde_json::{json, Serializer, Value};
use starknet_crypto::FieldElement;

use crate::hash::{pedersen_hash_array, StarkFelt, StarkHash};
use crate::serde_utils::{InnerDeserializationError, InnerSerializationError};
use crate::transaction::{Calldata, ContractAddressSalt};
use crate::StarknetApiError;

Expand Down Expand Up @@ -73,6 +75,162 @@ pub fn calculate_contract_address(
ContractAddress::try_from(StarkFelt::from(address))
}

fn compute_class_hash_from_json(contract_class: &Value) -> Result<String, StarknetApiError> {
let mut abi_json = json!({
"abi": contract_class.get("abi").unwrap_or(&Value::Null),
"program": contract_class.get("program").unwrap_or(&Value::Null)
});

let program_json = abi_json
.get_mut("program")
.ok_or(InnerDeserializationError::MissingKey { key: "program".to_string() })?;

let debug_info_json = program_json.get_mut("debug_info");
if debug_info_json.is_some() {
program_json
.as_object_mut()
.ok_or(InnerDeserializationError::UnexpectedType { expected: "object".to_string() })?
.insert("debug_info".to_owned(), serde_json::Value::Null);
}

let mut new_object = serde_json::Map::<String, Value>::new();
let res = crate::utils::traverse_and_exclude_recursively(
&abi_json,
&mut new_object,
&|key, value| {
return (key == "attributes" || key == "accessible_scopes")
&& value.is_array()
&& value.as_array().expect("Not a valid JSON array").is_empty();
},
);

let mut writer = Vec::with_capacity(128);
let mut serializer =
Serializer::with_formatter(&mut writer, crate::serde_utils::StarknetFormatter);
res.serialize(&mut serializer).map_err(|_| InnerSerializationError::FormatterError {
formatter: "StarknetFormatter".to_string(),
})?;

let str_json = String::from_utf8(writer).map_err(|_| InnerSerializationError::Custom {
msg: "Cant convert from bytes to UTF-8 JSON string".to_string(),
})?;

Ok(crate::hash::sn_keccak(str_json.as_bytes()))
}

fn entry_points_hash_by_type_from_json(
contract_class: &Value,
entry_point_type: &str,
) -> Result<StarkFelt, StarknetApiError> {
let felts: Result<Vec<_>, _> = contract_class
.get("entry_points_by_type")
.unwrap_or(&serde_json::Value::Null)
.get(entry_point_type)
.unwrap_or(&serde_json::Value::Null)
.as_array()
.unwrap_or(&Vec::<serde_json::Value>::new())
.clone()
.into_iter()
.flat_map(|entry| {
let selector = get_starkfelt_from_json(&entry, "selector");
let offset = get_starkfelt_from_json(&entry, "offset");

vec![selector, offset]
})
.collect();

Ok(pedersen_hash_array(&felts?))
}

fn get_starkfelt_from_json(json: &Value, key: &str) -> Result<StarkFelt, StarknetApiError> {
StarkFelt::try_from(
json.get(key)
.ok_or(InnerDeserializationError::MissingKey { key: key.to_string() })?
.as_str()
.ok_or(InnerDeserializationError::UnexpectedType { expected: "string".to_string() })?,
)
}

pub fn compute_contract_class_hash_v0(
contract_class: &serde_json::Value,
) -> Result<ClassHash, StarknetApiError> {
// api version
let api_version = StarkFelt::try_from(format!("0x{}", hex::encode([0u8])).as_str())?;

// external entry points hash
let external_entry_points_hash =
entry_points_hash_by_type_from_json(contract_class, "EXTERNAL")?;

// l1 handler entry points hash
let l1_entry_points_hash = entry_points_hash_by_type_from_json(contract_class, "L1_HANDLER")?;

// constructor handler entry points hash
let constructor_entry_points_hash =
entry_points_hash_by_type_from_json(contract_class, "CONSTRUCTOR")?;

// builtins hash
let builtins_encoded: Result<Vec<_>, _> = contract_class
.get("program")
.unwrap_or(&serde_json::Value::Null)
.get("builtins")
.unwrap_or(&serde_json::Value::Null)
.as_array()
.unwrap_or(&Vec::<serde_json::Value>::new())
.clone()
.into_iter()
.map(|str| {
let json_str = str.as_str().ok_or(InnerDeserializationError::UnexpectedType {
expected: "string".to_string(),
})?;
let hex_str = json_str
.as_bytes()
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<String>>()
.join("");

Ok::<String, StarknetApiError>(format!("0x{}", hex_str))
})
.collect();

let builtins_encoded_as_felts: Result<Vec<_>, _> =
builtins_encoded?.into_iter().map(|s| StarkFelt::try_from(s.as_str())).collect();

let builtins_hash = pedersen_hash_array(&builtins_encoded_as_felts?);

// hinted class hash
let hinted_class_hash = compute_class_hash_from_json(contract_class)?;

// program data hash
let program_data_felts: Result<Vec<_>, _> = contract_class
.get("program")
.unwrap_or(&Value::Null)
.get("data")
.unwrap_or(&Value::Null)
.as_array()
.unwrap_or(&Vec::<Value>::new())
.clone()
.into_iter()
.map(|str| {
StarkFelt::try_from(str.as_str().ok_or(InnerDeserializationError::UnexpectedType {
expected: "string".to_string(),
})?)
})
.collect();

let program_data_hash = pedersen_hash_array(&program_data_felts?);

Ok(ClassHash(pedersen_hash_array(&vec![
api_version,
external_entry_points_hash,
l1_entry_points_hash,
constructor_entry_points_hash,
builtins_hash,
StarkFelt::try_from(hinted_class_hash.as_str())?,
program_data_hash,
])))
}

/// The hash of a ContractClass.
#[derive(
Debug,
Expand Down
14 changes: 14 additions & 0 deletions src/core_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,17 @@ fn test_calculate_contract_address() {

assert_eq!(actual_address, expected_address);
}

#[test]
fn contract_class_hash_generation() {
let data_str = std::fs::read_to_string("./resources/contract_compiled.json").unwrap();
let contract_class_json: serde_json::Value = serde_json::from_str(&data_str).unwrap();
let expected_class_hash = ClassHash(
StarkHash::try_from("0x399998c787e0a063c3ac1d2abac084dcbe09954e3b156d53a8c43a02aa27d35")
.unwrap(),
);

let resulted_class_hash =
crate::core::compute_contract_class_hash_v0(&contract_class_json).unwrap();
assert_eq!(resulted_class_hash, expected_class_hash);
}
8 changes: 4 additions & 4 deletions src/deprecated_contract_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ impl TryFrom<String> for EntryPointOffset {

pub fn number_or_string<'de, D: Deserializer<'de>>(deserializer: D) -> Result<usize, D::Error> {
let usize_value = match Value::deserialize(deserializer)? {
Value::Number(number) => {
number.as_u64().ok_or(DeserializationError::custom("Cannot cast number to usize."))?
as usize
}
Value::Number(number) => number
.as_u64()
.ok_or_else(|| DeserializationError::custom("Cannot cast number to usize."))?
as usize,
Value::String(s) => hex_string_try_into_usize(&s).map_err(DeserializationError::custom)?,
_ => return Err(DeserializationError::custom("Cannot cast value into usize.")),
};
Expand Down
43 changes: 43 additions & 0 deletions src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use std::fmt::{Debug, Display};
use std::io::Error;

use serde::{Deserialize, Serialize};
use sha3::Digest;
use starknet_crypto::{pedersen_hash as starknet_crypto_pedersen_hash, FieldElement};
use web3::types::U256;

use crate::serde_utils::{
bytes_from_hex_str, hex_str_from_bytes, BytesAsHex, NonPrefixedBytesAsHex, PrefixedBytesAsHex,
Expand Down Expand Up @@ -43,12 +45,41 @@ pub fn pedersen_hash_array(felts: &[StarkFelt]) -> StarkHash {
pedersen_hash(&current_hash, &data_len)
}

/// Starknet Keccak Hash
pub fn sn_keccak(data: &[u8]) -> String {
let keccak256 = sha3::Keccak256::digest(data);
let number = U256::from_big_endian(keccak256.as_slice());
let mask = U256::pow(U256::from(2), U256::from(250)) - U256::from(1);
let masked_number = number & mask;
let mut res_bytes: [u8; 32] = [0; 32];
masked_number.to_big_endian(&mut res_bytes);
format!("0x{}", hex::encode(res_bytes).trim_start_matches('0'))
}

// TODO: Move to a different crate.
/// The StarkNet [field element](https://docs.starknet.io/documentation/architecture_and_concepts/Hashing/hash-functions/#domain_and_range).
#[derive(Copy, Clone, Eq, PartialEq, Default, Hash, Deserialize, Serialize, PartialOrd, Ord)]
#[serde(try_from = "PrefixedBytesAsHex<32_usize>", into = "PrefixedBytesAsHex<32_usize>")]
pub struct StarkFelt([u8; 32]);

#[derive(Clone, Copy, Eq, PartialEq, Default)]
pub struct StarkFeltAsDecimal(U256);

impl Serialize for StarkFeltAsDecimal {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.0.to_string())
}
}

impl Display for StarkFeltAsDecimal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_string())
}
}

impl StarkFelt {
/// Returns a new [`StarkFelt`].
pub fn new(bytes: [u8; 32]) -> Result<StarkFelt, StarknetApiError> {
Expand Down Expand Up @@ -200,6 +231,18 @@ impl TryFrom<StarkFelt> for usize {
}
}

impl From<StarkFelt> for U256 {
fn from(felt: StarkFelt) -> Self {
web3::types::U256::from_big_endian(&felt.0)
}
}

impl From<StarkFelt> for StarkFeltAsDecimal {
fn from(felt: StarkFelt) -> Self {
StarkFeltAsDecimal(U256::from(felt))
}
}

impl Debug for StarkFelt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.str_format(f)
Expand Down
12 changes: 11 additions & 1 deletion src/hash_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::hash::{pedersen_hash, pedersen_hash_array, StarkFelt};
use crate::hash::{pedersen_hash, pedersen_hash_array, StarkFelt, StarkFeltAsDecimal};
use crate::stark_felt;

#[test]
Expand Down Expand Up @@ -67,3 +67,13 @@ fn hash_serde() {
assert_eq!(bytes, d.0);
}
}

#[test]
fn stark_felt_from_hex_to_decimal() {
let felt = stark_felt!("0x264d6571d5f186bab2a9d5d8d30aa38bf5502bc4354870edbfe194c6f655c9b");
let felt_decimal = StarkFeltAsDecimal::from(felt);
assert_eq!(
felt_decimal.to_string(),
"1082789725971120866445625682125121757273101071727151005357998861560691645595"
);
}
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ pub mod hash;
pub mod serde_utils;
pub mod state;
pub mod transaction;
pub mod utils;

use std::num::ParseIntError;

use serde_utils::InnerDeserializationError;
use serde_utils::{InnerDeserializationError, InnerSerializationError};

/// The error type returned by StarknetApi.
#[derive(thiserror::Error, Clone, Debug)]
Expand All @@ -26,4 +27,7 @@ pub enum StarknetApiError {
/// Error when serializing into number.
#[error(transparent)]
ParseIntError(#[from] ParseIntError),
/// Error when serializing
#[error(transparent)]
InnerSerialization(#[from] InnerSerializationError),
}
Loading