Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion crates/starknet-types-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ serde = { version = "1", optional = true, default-features = false, features = [
"alloc", "derive"
] }
lambdaworks-crypto = { version = "0.12.0", default-features = false, optional = true }
parity-scale-codec = { version = "3.6", default-features = false, optional = true }
parity-scale-codec = { version = "3.6", default-features = false, features = ["derive"], optional = true }
lazy_static = { version = "1.5", default-features = false, optional = true }
zeroize = { version = "1.8.1", default-features = false, optional = true }
subtle = { version = "2.6.1", default-features = false, optional = true }
Expand Down
125 changes: 125 additions & 0 deletions crates/starknet-types-core/src/contract_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! A starknet contract address
//!
//! In starknet valid contract addresses exists as a subset of the type `Felt`.
//! Therefore some checks must be done in order to produce protocol valid addresses.
//! This module provides this logic as a type `ContractAddress`, that can garantee the validity of the address.
//! It also comes with some quality of life methods.

use core::str::FromStr;

use crate::{
felt::Felt,
patricia_key::{
PatriciaKey, PatriciaKeyFromFeltError, PatriciaKeyFromStrError, PATRICIA_KEY_UPPER_BOUND,
},
};

#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(
feature = "parity-scale-codec",
derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
)]
pub struct ContractAddress(PatriciaKey);

impl core::fmt::Display for ContractAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}

impl AsRef<Felt> for ContractAddress {
fn as_ref(&self) -> &Felt {
self.0.as_ref()
}
}

impl From<ContractAddress> for Felt {
fn from(value: ContractAddress) -> Self {
value.0.into()
}
}

impl AsRef<PatriciaKey> for ContractAddress {
fn as_ref(&self) -> &PatriciaKey {
&self.0
}
}

impl From<ContractAddress> for PatriciaKey {
fn from(value: ContractAddress) -> Self {
value.0
}
}

impl From<PatriciaKey> for ContractAddress {
fn from(value: PatriciaKey) -> Self {
ContractAddress(value)
}
}

impl TryFrom<Felt> for ContractAddress {
type Error = PatriciaKeyFromFeltError;

fn try_from(value: Felt) -> Result<Self, Self::Error> {
Ok(ContractAddress(PatriciaKey::try_from(value)?))
}
}

impl Felt {
/// Validates that a Felt value represents a valid Starknet contract address.
pub fn is_valid_contract_address(&self) -> bool {
self < &PATRICIA_KEY_UPPER_BOUND
}
}

impl FromStr for ContractAddress {
type Err = PatriciaKeyFromStrError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ContractAddress(PatriciaKey::from_str(s)?))
}
}

impl ContractAddress {
pub const fn from_hex_unchecked(s: &'static str) -> ContractAddress {
let felt = PatriciaKey::from_hex_unchecked(s);

ContractAddress(felt)
}
}

#[cfg(test)]
mod test {
#[cfg(feature = "alloc")]
pub extern crate alloc;
use proptest::prelude::*;

use crate::{
contract_address::ContractAddress, felt::Felt, patricia_key::PATRICIA_KEY_UPPER_BOUND,
};

#[test]
fn basic_values() {
assert!(ContractAddress::try_from(Felt::ZERO).is_err());
assert!(ContractAddress::try_from(Felt::ONE).is_err());
assert!(ContractAddress::try_from(PATRICIA_KEY_UPPER_BOUND).is_err());

let felt = Felt::TWO;
let contract_address = ContractAddress::try_from(felt).unwrap();
assert_eq!(Felt::from(contract_address), felt);
}

proptest! {
#[test]
fn is_valid_match_try_into(ref x in any::<Felt>()) {
if x.is_valid_contract_address() {
prop_assert!(ContractAddress::try_from(*x).is_ok());
} else {
prop_assert!(ContractAddress::try_from(*x).is_err());
}
}
}
}
3 changes: 3 additions & 0 deletions crates/starknet-types-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pub mod hash;
pub mod felt;
pub mod qm31;

pub mod contract_address;
pub mod patricia_key;
pub mod regular_contract_address;
#[cfg(any(feature = "std", feature = "alloc"))]
pub mod short_string;
pub mod u256;
110 changes: 110 additions & 0 deletions crates/starknet-types-core/src/patricia_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! The key of one of starknet state tree
//!
//! https://docs.starknet.io/learn/protocol/state
//! The state of the starknet blockchain (contracts declared, contracts deployed, storage of each contract),
//! is represented as multiple binary Merkle-Patricia trees.
//! Those trees have an height of 251, which means that they contains at most 2^251 values.
//! The keys to those values are represented as `Felt`, with range [0, PATRICIA_KEY_UPPER_BOUND).
//! Therefore not every `Felt` is a valid `PatriciaKey`,
//! and we can use the `PatriciaKey` type to enfoce type safety in our code.

use core::str::FromStr;

use crate::felt::Felt;

pub const PATRICIA_KEY_UPPER_BOUND: Felt =
Felt::from_hex_unchecked("0x800000000000000000000000000000000000000000000000000000000000000");

#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(
feature = "parity-scale-codec",
derive(parity_scale_codec::Encode, parity_scale_codec::Decode)
)]
pub struct PatriciaKey(Felt);

impl core::fmt::Display for PatriciaKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}

impl AsRef<Felt> for PatriciaKey {
fn as_ref(&self) -> &Felt {
&self.0
}
}

impl From<PatriciaKey> for Felt {
fn from(value: PatriciaKey) -> Self {
value.0
}
}

#[derive(Debug, Clone, Copy)]
pub struct PatriciaKeyFromFeltError(Felt);

impl core::fmt::Display for PatriciaKeyFromFeltError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"invalid felt value for patricia key. Upper non-inclusinve bound is 2^251 got {:#x}",
self.0
)
}
}

#[cfg(feature = "std")]
impl std::error::Error for PatriciaKeyFromFeltError {}

impl TryFrom<Felt> for PatriciaKey {
type Error = PatriciaKeyFromFeltError;

fn try_from(value: Felt) -> Result<Self, Self::Error> {
if value >= PATRICIA_KEY_UPPER_BOUND {
return Err(PatriciaKeyFromFeltError(value));
}

Ok(PatriciaKey(value))
}
}

#[derive(Debug)]
pub enum PatriciaKeyFromStrError {
BadFelt(<Felt as FromStr>::Err),
BadKey(PatriciaKeyFromFeltError),
}

impl core::fmt::Display for PatriciaKeyFromStrError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PatriciaKeyFromStrError::BadFelt(e) => write!(f, "invalid felt string: {e}"),
PatriciaKeyFromStrError::BadKey(e) => write!(f, "invalid address value: {e}"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for PatriciaKeyFromStrError {}

impl FromStr for PatriciaKey {
type Err = PatriciaKeyFromStrError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let felt = Felt::from_str(s).map_err(PatriciaKeyFromStrError::BadFelt)?;
let contract_address =
PatriciaKey::try_from(felt).map_err(PatriciaKeyFromStrError::BadKey)?;

Ok(contract_address)
}
}

impl PatriciaKey {
pub const fn from_hex_unchecked(s: &'static str) -> PatriciaKey {
let felt = Felt::from_hex_unchecked(s);

PatriciaKey(felt)
}
}
Loading