From 513ebb7ce91a45541de6d564e8e9dfe0f8b3f03c Mon Sep 17 00:00:00 2001 From: Jonathan LEI Date: Mon, 29 Jul 2024 00:52:27 +0800 Subject: [PATCH] docs: add docs for `starknet-core` --- starknet-core/src/chain_id.rs | 4 + starknet-core/src/crypto.rs | 20 +++ starknet-core/src/lib.rs | 9 +- starknet-core/src/serde/byte_array.rs | 3 + starknet-core/src/serde/mod.rs | 2 + .../src/serde/unsigned_field_element.rs | 4 + starknet-core/src/types/codegen.rs | 3 +- starknet-core/src/types/contract/legacy.rs | 155 ++++++++++++++++- starknet-core/src/types/contract/mod.rs | 162 +++++++++++++++++- starknet-core/src/types/eth_address.rs | 10 ++ starknet-core/src/types/execution_result.rs | 14 +- starknet-core/src/types/hash_256.rs | 9 + starknet-core/src/types/mod.rs | 120 ++++++++++++- starknet-core/src/types/msg.rs | 6 + starknet-core/src/types/receipt_block.rs | 15 +- starknet-core/src/types/u256.rs | 46 ++--- starknet-core/src/utils.rs | 36 +++- 17 files changed, 584 insertions(+), 34 deletions(-) diff --git a/starknet-core/src/chain_id.rs b/starknet-core/src/chain_id.rs index 715dc437..8ec150bc 100644 --- a/starknet-core/src/chain_id.rs +++ b/starknet-core/src/chain_id.rs @@ -1,5 +1,6 @@ use starknet_types_core::felt::Felt; +/// The chain identifier for Starknet Mainnet. A Cairo short string encoding of `SN_MAIN`. pub const MAINNET: Felt = Felt::from_raw([ 502562008147966918, 18446744073709551615, @@ -7,6 +8,7 @@ pub const MAINNET: Felt = Felt::from_raw([ 17696389056366564951, ]); +/// The chain identifier for Starknet Goerli. A Cairo short string encoding of `SN_GOERLI`. #[deprecated = "The Goerli testnet has been shutdown"] pub const TESTNET: Felt = Felt::from_raw([ 398700013197595345, @@ -15,6 +17,7 @@ pub const TESTNET: Felt = Felt::from_raw([ 3753493103916128178, ]); +/// The chain identifier for Starknet Goerli 2. A Cairo short string encoding of `SN_GOERLI2`. #[deprecated = "The Goerli testnet has been shutdown"] pub const TESTNET2: Felt = Felt::from_raw([ 33650220878420990, @@ -23,6 +26,7 @@ pub const TESTNET2: Felt = Felt::from_raw([ 1663542769632127759, ]); +/// The chain identifier for Starknet Sepolia. A Cairo short string encoding of `SN_SEPOLIA`. pub const SEPOLIA: Felt = Felt::from_raw([ 507980251676163170, 18446744073709551615, diff --git a/starknet-core/src/crypto.rs b/starknet-core/src/crypto.rs index 1977f4da..fbaf6973 100644 --- a/starknet-core/src/crypto.rs +++ b/starknet-core/src/crypto.rs @@ -6,16 +6,23 @@ use starknet_crypto::{rfc6979_generate_k, sign, verify, SignError, VerifyError}; mod errors { use core::fmt::{Display, Formatter, Result}; + /// Errors when performing ECDSA [`sign`](fn.ecdsa_sign) operations. #[derive(Debug)] pub enum EcdsaSignError { + /// The message hash is not in the range of `[0, 2^251)`. MessageHashOutOfRange, } #[derive(Debug)] + /// Errors when performing ECDSA [`verify`](fn.ecdsa_verify) operations. pub enum EcdsaVerifyError { + /// The message hash is not in the range of `[0, 2^251)`. MessageHashOutOfRange, + /// The public key is not a valid point on the STARK curve. InvalidPublicKey, + /// The `r` value is not in the range of `[0, 2^251)`. SignatureROutOfRange, + /// The `s` value is not in the range of `[0, 2^251)`. SignatureSOutOfRange, } @@ -46,6 +53,16 @@ mod errors { } pub use errors::{EcdsaSignError, EcdsaVerifyError}; +/// Computes the Pedersen hash of a list of [`Felt`]. +/// +/// The hash is computed by starting with `0`, hashing it recursively against all elements in +/// the list, and finally also hashing against the length of the list. +/// +/// For example, calling `compute_hash_on_elements([7, 8])` would return: +/// +/// ```markdown +/// pedersen_hash(pedersen_hash(pedersen_hash(0, 7)), 8), 2) +/// ``` pub fn compute_hash_on_elements<'a, ESI, II>(data: II) -> Felt where ESI: ExactSizeIterator, @@ -62,6 +79,8 @@ where pedersen_hash(¤t_hash, &data_len) } +/// Signs a hash using deterministic ECDSA on the STARK curve. The signature returned can be used +/// to recover the public key. pub fn ecdsa_sign( private_key: &Felt, message_hash: &Felt, @@ -89,6 +108,7 @@ pub fn ecdsa_sign( } } +/// Verified an ECDSA signature on the STARK curve. pub fn ecdsa_verify( public_key: &Felt, message_hash: &Felt, diff --git a/starknet-core/src/lib.rs b/starknet-core/src/lib.rs index f689809b..676c1bed 100644 --- a/starknet-core/src/lib.rs +++ b/starknet-core/src/lib.rs @@ -1,15 +1,22 @@ +//! Core data types and utilities for Starknet. + +#![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::comparison_chain)] -#![doc = include_str!("../README.md")] +/// Module containing custom serialization/deserialization implementations. pub mod serde; +/// Module containing core types for representing objects in Starknet. pub mod types; +/// High-level utilities for cryptographic operations used in Starknet. pub mod crypto; +/// Utilities for performing commonly used algorithms in Starknet. pub mod utils; +/// Chain IDs for commonly used public Starknet networks. pub mod chain_id; extern crate alloc; diff --git a/starknet-core/src/serde/byte_array.rs b/starknet-core/src/serde/byte_array.rs index 0d2bb46e..70b6ed6e 100644 --- a/starknet-core/src/serde/byte_array.rs +++ b/starknet-core/src/serde/byte_array.rs @@ -1,3 +1,4 @@ +/// Serializing and deserializing [`Vec`] with base64 encoding. pub mod base64 { use alloc::{fmt::Formatter, format, vec::*}; @@ -6,6 +7,7 @@ pub mod base64 { struct Base64Visitor; + /// Serializes [`Vec`] as base64 string. pub fn serialize(value: T, serializer: S) -> Result where S: Serializer, @@ -14,6 +16,7 @@ pub mod base64 { serializer.serialize_str(&STANDARD.encode(value.as_ref())) } + /// Deserializes [`Vec`] from base64 string. pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, diff --git a/starknet-core/src/serde/mod.rs b/starknet-core/src/serde/mod.rs index c414c706..abe1d561 100644 --- a/starknet-core/src/serde/mod.rs +++ b/starknet-core/src/serde/mod.rs @@ -1,5 +1,7 @@ +/// Custom serialization/deserialization implementations for [`Vec`]. pub mod byte_array; +/// Custom serialization/deserialization implementations for [`Felt`]. pub mod unsigned_field_element; pub(crate) mod num_hex; diff --git a/starknet-core/src/serde/unsigned_field_element.rs b/starknet-core/src/serde/unsigned_field_element.rs index 87f311cb..f42606a8 100644 --- a/starknet-core/src/serde/unsigned_field_element.rs +++ b/starknet-core/src/serde/unsigned_field_element.rs @@ -12,12 +12,16 @@ use starknet_types_core::felt::Felt; const PRIME: U256 = U256::from_be_hex("0800000000000011000000000000000000000000000000000000000000000001"); +/// Serialize/deserialize [`Felt`] as hex strings. For use with `serde_with`. #[derive(Debug)] pub struct UfeHex; +/// Serialize/deserialize [`Option`] as hex strings. For use with `serde_with`. #[derive(Debug)] pub struct UfeHexOption; +/// Serialize/deserialize [`Option`] as hex strings in a pending block hash context. For use +/// with `serde_with`. #[derive(Debug)] pub struct UfePendingBlockHash; diff --git a/starknet-core/src/types/codegen.rs b/starknet-core/src/types/codegen.rs index 2d7e0d02..a4483a37 100644 --- a/starknet-core/src/types/codegen.rs +++ b/starknet-core/src/types/codegen.rs @@ -3,7 +3,7 @@ // https://github.com/xJonathanLEI/starknet-jsonrpc-codegen // Code generated with version: -// https://github.com/xJonathanLEI/starknet-jsonrpc-codegen#fbd3aed2a08d6b29328e87ee0bbfb7e80f7051b0 +// https://github.com/xJonathanLEI/starknet-jsonrpc-codegen#f1278dfb2ae57d319093421c038f6ec7a3dfba2f // These types are ignored from code generation. Implement them manually: // - `RECEIPT_BLOCK` @@ -24,6 +24,7 @@ // - `TXN` // - `TXN_RECEIPT` +#![allow(missing_docs)] #![allow(clippy::doc_markdown)] #![allow(clippy::missing_const_for_fn)] diff --git a/starknet-core/src/types/contract/legacy.rs b/starknet-core/src/types/contract/legacy.rs index 3803a218..4608bd7a 100644 --- a/starknet-core/src/types/contract/legacy.rs +++ b/starknet-core/src/types/contract/legacy.rs @@ -26,150 +26,234 @@ use flate2::{write::GzEncoder, Compression}; const API_VERSION: Felt = Felt::ZERO; +/// A legacy (Cairo 0) contract class in a representation identical to the compiler output. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyContractClass { + /// Contract ABI. pub abi: Vec, + /// Contract entrypoints. pub entry_points_by_type: RawLegacyEntryPoints, + /// The Cairo program of the contract containing the actual bytecode. pub program: LegacyProgram, } +/// Legacy (Cairo 0) contract entrypoints by types. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct RawLegacyEntryPoints { + /// Entrypoints of type `CONSTRUCTOR` used during contract deployment. pub constructor: Vec, + /// Entrypoints of type `EXTERNAL` used for invocations from outside contracts. pub external: Vec, + /// Entrypoints of type `L1_HANDLER` used for handling L1-to-L2 messages. pub l1_handler: Vec, } +/// Legacy (Cairo 0) program containing bytecode and other data necessary for execution. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyProgram { + /// Attributes that provide additional context for certain sections of the bytecode. #[serde(skip_serializing_if = "Option::is_none")] pub attributes: Option>, + /// The list of Cairo builtins this program has access to. pub builtins: Vec, // This field was introduced in Cairo 0.10.0. By making it optional we're keeping compatibility // with older artifacts. This decision should be reviewd in the future. + /// Version of the compiler used to compile this contract. #[serde(skip_serializing_if = "Option::is_none")] pub compiler_version: Option, + /// The Cairo assembly bytecode of the contract. #[serde_as(as = "Vec")] pub data: Vec, + /// Debug information which is optionally emitted by the compiler. This field is not used for + /// class declaration or class hash calculation. pub debug_info: Option, + /// Legacy hints for non-determinism. pub hints: BTreeMap>, + /// A map of identifiers by name. pub identifiers: BTreeMap, + /// The main scope/namespace where all identifiers this program defines live in, usually + /// `__main__`. pub main_scope: String, // Impossible to use [Felt] here as by definition field elements are smaller // than prime + /// The STARK field prime. pub prime: String, + /// Data for tracking + /// [references](https://docs.cairo-lang.org/how_cairo_works/consts.html#references). pub reference_manager: LegacyReferenceManager, } +/// An legacy (Cairo 0) contract entrypoint for translating a selector to a bytecode offset. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct RawLegacyEntryPoint { + /// Offset in the bytecode. pub offset: LegacyEntrypointOffset, + /// Selector of the entrypoint, usually computed as the Starknet Keccak of the function name. #[serde_as(as = "UfeHex")] pub selector: Felt, } +/// Legacy (Cairo 0) program attribute that provide additional context for certain sections of the +/// bytecode. +/// +/// Attributes are usually used for providing error messages when an assertion fails. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyAttribute { + /// The scopes from which the attribute is accessible. #[serde(default)] pub accessible_scopes: Vec, + /// The ending PC of the segment that has access to the attribute. pub end_pc: u64, + /// Data needed for tracking the allocation pointer. #[serde(skip_serializing_if = "Option::is_none")] pub flow_tracking_data: Option, + /// Name of the attribute. pub name: String, + /// The starting PC of the segment that has access to the attribute. pub start_pc: u64, + /// Value of the attribute. pub value: String, } +/// Debug information generated by the legacy (Cairo 0) compiler. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyDebugInfo { /// A partial map from file name to its content. Files that are not in the map, are assumed to /// exist in the file system. pub file_contents: BTreeMap, - /// A map from (relative) PC to the location of the instruction + /// A map from (relative) PC to the location of the instruction. pub instruction_locations: BTreeMap, } +/// Legacy (Cairo 0) hints for introducing non-determinism into a Cairo program. +/// +/// These hints are implemented in Python that execute arbitrary code to fill Cairo VM memory. In +/// a public network like Starknet, a predefined list of hints are whitelisted to prevent deployment +/// of malicious code. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyHint { + /// The scopes from which the hint is accessible. pub accessible_scopes: Vec, + /// The Python code of the hint. pub code: String, + /// Data needed for tracking the allocation pointer. pub flow_tracking_data: LegacyFlowTrackingData, } +/// Legacy (Cairo 0) program identifiers. +/// +/// These are needed mostly to allow Python hints to work, as hints are allowed to reference Cairo +/// identifiers (e.g. variables) by name, which would otherwise be lost during compilation. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyIdentifier { + /// Decorators of the identifier, used for functions. #[serde(skip_serializing_if = "Option::is_none")] pub decorators: Option>, + /// The Cairo type, used for type definitions. #[serde(skip_serializing_if = "Option::is_none")] pub cairo_type: Option, + /// The fully-qualified name, used for struct definitions. #[serde(skip_serializing_if = "Option::is_none")] pub full_name: Option, + /// The list of members, used for struct definitions. #[serde(skip_serializing_if = "Option::is_none")] pub members: Option>, + /// The list of references, used for references. #[serde(skip_serializing_if = "Option::is_none")] pub references: Option>, + /// The size in the number of field elements, used for struct definitions. #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, + /// The program counter, used for functions. #[serde(skip_serializing_if = "Option::is_none")] pub pc: Option, + /// The fully-qualified name of the identifier that this identifier points to, used for + /// aliases. #[serde(skip_serializing_if = "Option::is_none")] pub destination: Option, + /// Type of the identifier. pub r#type: String, + /// Value of the identifier, used for constants. #[serde(skip_serializing_if = "Option::is_none")] pub value: Option>, } +/// Data needed for tracking +/// [references](https://docs.cairo-lang.org/how_cairo_works/consts.html#references). #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyReferenceManager { + /// The list of references. pub references: Vec, } -/// This field changed from hex string to number on 0.11.0. +/// The legacy (Cairo 0) contract entrypoint offset, in either hexadecimal or numeric +/// representation. +/// +/// This type is needed as the entrypoint offset field changed from hex string to number on 0.11.0. +/// The type allows serializing older contracts in their original forms. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] #[serde(untagged)] pub enum LegacyEntrypointOffset { + /// Offset with hexadecimal representation. U64AsHex(#[serde(with = "u64_hex")] u64), + /// Offset with numeric representation. U64AsInt(u64), } +/// Legacy (Cairo 0) instruction location for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyInstructionLocation { + /// The scopes from which the instruction is accessible. pub accessible_scopes: Vec, // This field is serialized as `null` instead of skipped + /// Data needed for tracking the allocation pointer. pub flow_tracking_data: Option, + /// Physical code locations of hints in the source . pub hints: Vec, + /// Physical code location of the instruction in the source. pub inst: LegacyLocation, } +/// Legacy (Cairo 0) struct member as part of a struct definition identifier. Used in +/// [`LegacyIdentifier`] for enabling hints to access identifiers by name. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyIdentifierMember { + /// The Cairo type of this struct field. pub cairo_type: String, + /// Offset of the field calculated as the total size of all the fields before this member in the + /// number of field elements. pub offset: u64, } +/// Cairo 0 [references](https://docs.cairo-lang.org/how_cairo_works/consts.html#references). #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyReference { + /// Data needed for tracking the allocation pointer. pub ap_tracking_data: LegacyApTrackingData, + /// Program counter value. pub pc: u64, + /// Value of the reference. pub value: String, } +// Missing docs allowed as it's unclear what exactly how type works in the Cairo program. +#[allow(missing_docs)] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyFlowTrackingData { @@ -177,26 +261,37 @@ pub struct LegacyFlowTrackingData { pub reference_ids: BTreeMap, } +/// Physical location of a legacy (Cairo 0) hint in source for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyHintLocation { + /// The physical location of the hint. pub location: LegacyLocation, - /// The number of new lines following the "%{" symbol + /// The number of new lines following the "%{" symbol. pub n_prefix_newlines: u64, } +/// The physical location in source of a certain code segment for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyLocation { + /// The ending column number. pub end_col: u64, + /// The ending line number. pub end_line: u64, + /// The file path or content. pub input_file: LegacyInputFile, + /// Location of the parent instruction, if any. #[serde(skip_serializing_if = "Option::is_none")] pub parent_location: Option, + /// The starting column number. pub start_col: u64, + /// The starting line number. pub start_line: u64, } +// Missing docs allowed as it's unclear what exactly how type works in the Cairo program. +#[allow(missing_docs)] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyApTrackingData { @@ -204,73 +299,118 @@ pub struct LegacyApTrackingData { pub offset: u64, } +/// Input file path or content for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyInputFile { + /// Path to file in the file system. #[serde(skip_serializing_if = "Option::is_none")] pub filename: Option, + /// Full content of the file, typically for ephemeral files. #[serde(skip_serializing_if = "Option::is_none")] pub content: Option, } +/// Location of the parent instruction for use in debug info. +/// +/// It's used (?) for generating human-readable stack traces. #[derive(Debug, Clone)] pub struct LegacyParentLocation { + /// Location of the parent instruction. pub location: Box, + /// A human-readable remark usually in the form similar to "while trying to xxx". pub remark: String, } +/// Legacy (Cairo 0) contract ABI item. #[derive(Debug, Clone)] pub enum RawLegacyAbiEntry { + /// Constructor ABI entry. Constructor(RawLegacyConstructor), + /// Function ABI entry. Function(RawLegacyFunction), + /// Struct ABI entry. Struct(RawLegacyStruct), + /// L1 handler ABI entry. L1Handler(RawLegacyL1Handler), + /// Event ABI entry. Event(RawLegacyEvent), } +/// Legacy (Cairo 0) contract ABI representation of a constructor. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyConstructor { + /// Inputs to the constructor. pub inputs: Vec, + /// Name of the constructor. pub name: String, + /// Outputs of the constructor. pub outputs: Vec, } +/// Legacy (Cairo 0) contract ABI representation of a function. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RawLegacyFunction { + /// Inputs to the function. pub inputs: Vec, + /// Name of the function. pub name: String, + /// Outputs of the function. pub outputs: Vec, + /// State mutability of the function. + /// + /// Note that this is currently not enforced by the compiler. It's therefore only as accurate as + /// the code author annotating them is. #[serde(skip_serializing_if = "Option::is_none")] pub state_mutability: Option, } +/// Legacy (Cairo 0) contract ABI representation of a struct. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyStruct { + /// Fields of the struct. pub members: Vec, + /// Name of the struct. pub name: String, + /// Size of the struct in the number of field elements. pub size: u64, } +/// Legacy (Cairo 0) contract ABI representation of an L1 handler. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyL1Handler { + /// Inputs to the L1 handler function. pub inputs: Vec, + /// Name of the L1 handler function. pub name: String, + /// Outputs of the L1 handler function. pub outputs: Vec, } +/// Legacy (Cairo 0) contract ABI representation of an event. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyEvent { + /// Data of the events for the unindexed event fields. pub data: Vec, + /// Keys of the events. + /// + /// This usually includes at least one element as the Starknet Keccak of the event name. + /// Additional keys are used for indexed event fields, if any. pub keys: Vec, + /// Name of the events. pub name: String, } +/// Legacy (Cairo 0) contract ABI representation of a struct field. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct RawLegacyMember { + /// Name of the struct field. pub name: String, + /// Total size of the fields in the struct before this field. pub offset: u64, + /// Cairo type of the struct field. pub r#type: String, } @@ -394,6 +534,7 @@ impl<'de> Deserialize<'de> for RawLegacyAbiEntry { } impl LegacyContractClass { + /// Computes the class hash of the legacy (Cairo 0) class. pub fn class_hash(&self) -> Result { let mut elements = Vec::new(); @@ -449,6 +590,12 @@ impl LegacyContractClass { Ok(compute_hash_on_elements(&elements)) } + /// Computes the "hinted" class hash of the legacy (Cairo 0) class. + /// + /// This is known as the "hinted" hash as it isn't possible to directly calculate, and thus + /// prove the correctness of, this hash, since it involves JSON serialization. Instead, this + /// hash is always calculated outside of the Cairo VM, and then fed to the Cairo program as a + /// hinted value. pub fn hinted_class_hash(&self) -> Result { #[serde_as] #[derive(Serialize)] @@ -471,6 +618,7 @@ impl LegacyContractClass { Ok(starknet_keccak(serialized.as_bytes())) } + /// Compresses the legacy (Cairo 0) class with gzip, as needed for class declaration. #[cfg(feature = "std")] pub fn compress(&self) -> Result { Ok(CompressedLegacyContractClass { @@ -488,6 +636,7 @@ impl LegacyContractClass { } impl LegacyProgram { + /// Compresses the legacy (Cairo 0) program with gzip. #[cfg(feature = "std")] pub fn compress(&self) -> Result, CompressProgramError> { use std::io::Write; diff --git a/starknet-core/src/types/contract/mod.rs b/starknet-core/src/types/contract/mod.rs index ba1b17ba..8d50753f 100644 --- a/starknet-core/src/types/contract/mod.rs +++ b/starknet-core/src/types/contract/mod.rs @@ -32,33 +32,52 @@ const PREFIX_COMPILED_CLASS_V1: Felt = Felt::from_raw([ 2291010424822318237, ]); +/// Cairo contract artifact in a representation identical to the compiler output. #[derive(Debug, Clone, Serialize)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum ContractArtifact { + /// Sierra (Cairo 1) class. SierraClass(SierraClass), + /// Cairo assembly (CASM) class compiled from a Sierra class. CompiledClass(CompiledClass), + /// Legacy (Cairo 0) class LegacyClass(legacy::LegacyContractClass), } +/// A Sierra (Cairo 1) contract class in a representation identical to the compiler output. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct SierraClass { + /// Sierra bytecode. #[serde_as(as = "Vec")] pub sierra_program: Vec, + /// Sierra class debug info. pub sierra_program_debug_info: SierraClassDebugInfo, + /// Sierra version. pub contract_class_version: String, + /// Contract entrypoints. pub entry_points_by_type: EntryPointsByType, + /// Contract ABI. pub abi: Vec, } +/// A Cairo assembly (CASM) class compiled from a Sierra class. +/// +/// The Sierra to CASM process is needed as the Cairo VM can only execute CASM, not Sierra bytecode. +/// However, if direct deployment of CASM were allowed, unsafe (unprovable) programs could be +/// deployed to a public network allowing for DOS attacks. Instead, only Sierra, an intermedia +/// representation that always compiles to safe (provable) CASM, is allowed to be deployed. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct CompiledClass { + /// The STARK field prime. pub prime: String, + /// Version of the compiler used to compile this contract. pub compiler_version: String, + /// Cairo assembly bytecode. #[serde_as(as = "Vec")] pub bytecode: Vec, /// Represents the structure of the bytecode segments, using a nested list of segment lengths. @@ -66,189 +85,282 @@ pub struct CompiledClass { /// length 2 and the second is a node with 2 children of lengths 3 and 4. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub bytecode_segment_lengths: Vec, + /// Hints for non-determinism. pub hints: Vec, + /// Same as `hints` but represented in Python code, which can be generated by the compiler but + /// is off by default. pub pythonic_hints: Option>, + /// Contract entrypoints. pub entry_points_by_type: CompiledClassEntrypointList, } +/// Debug information optionally generated by the compiler for mapping IDs to human-readable names. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct SierraClassDebugInfo { + /// Mapping from type IDs to names. pub type_names: Vec<(u64, String)>, + /// Mapping from libfunc IDs to names. pub libfunc_names: Vec<(u64, String)>, + /// Mapping from user function IDs to names. pub user_func_names: Vec<(u64, String)>, } +/// Cairo assembly (CASM) contract entrypoints by types. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct CompiledClassEntrypointList { + /// Entrypoints of type `EXTERNAL` used for invocations from outside contracts. pub external: Vec, + /// Entrypoints of type `L1_HANDLER` used for handling L1-to-L2 messages. pub l1_handler: Vec, + /// Entrypoints of type `CONSTRUCTOR` used during contract deployment. pub constructor: Vec, } +/// Sierra (Cairo 1) contract ABI item. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub enum AbiEntry { + /// Function ABI entry. Function(AbiFunction), + /// Event ABI entry. Event(AbiEvent), + /// Struct ABI entry. Struct(AbiStruct), + /// Enum ABI entry. Enum(AbiEnum), + /// Constructor ABI entry. Constructor(AbiConstructor), + /// Impl ABI entry. Impl(AbiImpl), + /// Interface ABI entry. Interface(AbiInterface), + /// L1 handler ABI entry. L1Handler(AbiFunction), } +/// Cairo assembly (CASM) hints for introducing non-determinism into a Cairo program. +/// +/// Unlike legacy (Cairo 0) hints which are arbitrary Python code, hints compiled from Sierra +/// bytecode come from a predefined list of hint specifications (e.g. `TestLessThanOrEqual`). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Hint { + /// ID of the hint. pub id: u64, // For convenience we just treat it as an opaque JSON value here, unless a use case justifies // implementing the structure. (We no longer need the hints for the class hash anyways.) + /// Declarative specification of the hint. pub code: Vec, } +/// Same as [`Hint`] but is represented as Python code instead of a declarative specification. #[derive(Debug, Clone)] pub struct PythonicHint { + /// ID of the hint. pub id: u64, + /// Python code representation of the hint. pub code: Vec, } +/// An Cairo assembly (CASM) contract entrypoint for translating a selector to a bytecode offset. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct CompiledClassEntrypoint { + /// Selector of the entrypoint, usually computed as the Starknet Keccak of the function name. #[serde_as(as = "UfeHex")] pub selector: Felt, + /// Offset in the bytecode. pub offset: u64, + /// The list of Cairo builtins used with this entrypoint. pub builtins: Vec, } +/// Sierra (Cairo 1) contract ABI representation of a function. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiFunction { + /// Name of the function. pub name: String, + /// Inputs to the function. pub inputs: Vec, + /// Outputs of the function. pub outputs: Vec, + /// State mutability of the function. + /// + /// Note that this is currently not enforced by the compiler. It's therefore only as accurate as + /// the code author annotating them is. pub state_mutability: StateMutability, } +/// Sierra (Cairo 1) contract ABI representation of an event. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] #[serde(untagged)] pub enum AbiEvent { - /// Cairo 2.x ABI event entry + /// Cairo 2.x ABI event entry. Typed(TypedAbiEvent), - /// Cairo 1.x ABI event entry + /// Cairo 1.x ABI event entry. Untyped(UntypedAbiEvent), } +/// Cairo 2.x ABI event entry. #[derive(Debug, Clone, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub enum TypedAbiEvent { + /// An event definition that's a struct. Struct(AbiEventStruct), + /// An event definition that's an enum. Enum(AbiEventEnum), } +/// Cairo 1.x ABI event entry. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct UntypedAbiEvent { + /// Name of the event. pub name: String, + /// Fields of the event. pub inputs: Vec, } +/// An event definition that's a struct. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiEventStruct { + /// Name of the event struct. pub name: String, + /// Fields of the event struct. pub members: Vec, } +/// An event definition that's an enum. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiEventEnum { + /// Name of the event enum. pub name: String, + /// Variants of the event enum. pub variants: Vec, } +/// Sierra (Cairo 1) contract ABI representation of a struct. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiStruct { + /// Name of the struct. pub name: String, + /// Fields of the struct. pub members: Vec, } +/// Sierra (Cairo 1) contract ABI representation of a constructor. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiConstructor { + /// Name of the constructor. pub name: String, + /// Inputs to the constructor. pub inputs: Vec, } +/// Sierra (Cairo 1) contract ABI representation of an interface implementation. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiImpl { + /// Name of the interface implementation. pub name: String, + /// Name of the interface being implemented. pub interface_name: String, } +/// Sierra (Cairo 1) contract ABI representation of an interface. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiInterface { + /// Name of the interface. pub name: String, + /// The shape of the interface. pub items: Vec, } +/// Sierra (Cairo 1) contract ABI representation of an enum. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiEnum { + /// Name of the enum. pub name: String, + /// Variants of the enum. pub variants: Vec, } +/// A name and type pair for describing a struct field. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiNamedMember { + /// Name of the field. pub name: String, + /// Type of the field. pub r#type: String, } +/// An output from a contract function. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiOutput { + /// Type of the output. pub r#type: String, } +/// Struct field or enum variant of an event type. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct EventField { + /// Name of the field or variant. pub name: String, + /// Type of the field or variant. pub r#type: String, + /// The role this field or variant plays as part of an event emission. pub kind: EventFieldKind, } +/// State mutability of a function. +/// +/// Note that this is currently not enforced by the compiler. It's therefore only as accurate as the +/// code author annotating them is. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum StateMutability { + /// The function is *annotated* as potentially state-changing. External, + /// The function is *annotated* as view-only, without changing the state. View, } +/// The role an event struct field or enum variant plays during an event emission. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum EventFieldKind { + /// Emitted as part of the event keys. Annotated with `#[key]`. Key, + /// Emitted as part of the event data. Annotated with `#[data]`. Data, + /// Serialized as a nested event. Nested, + /// Serialize as a flat event. Annotated with `#[flat]`. Flat, } +/// A node in a bytecode segment length tree. #[derive(Debug, Clone)] pub enum IntOrList { + /// A leave node of the tree representing a segment. Int(u64), + /// A branch node of the tree. List(Vec), } @@ -290,41 +402,68 @@ mod errors { use alloc::string::*; use core::fmt::{Display, Formatter, Result}; + /// Errors computing a class hash. #[derive(Debug)] pub enum ComputeClassHashError { + /// An invalid Cairo builtin name is found. This can happen when computing hashes for legacy + /// (Cairo 0) or Cairo assembly (CASM) classes. InvalidBuiltinName, + /// The total bytecode length is different than the implied length from + /// `bytecode_segment_lengths`. This can happen when computing Cairo assembly (CASM) class + /// hashes. BytecodeSegmentLengthMismatch(BytecodeSegmentLengthMismatchError), + /// A bytecode segment specified in `bytecode_segment_lengths` is invalid. This can happen + /// when computing Cairo assembly (CASM) class hashes. InvalidBytecodeSegment(InvalidBytecodeSegmentError), + /// The bytecode segments specified in `bytecode_segment_lengths` do not cover the full + /// range of bytecode. This can happen when computing Cairo assembly (CASM) class hashes. PcOutOfRange(PcOutOfRangeError), + /// Json serialization error. This can happen when serializing the contract ABI, as required + /// for computing hashes for legacy (Cairo 0) and Sierra (Cairo 1) classes. Json(JsonError), } + /// Errors compressing a legacy (Cairo 0) class. #[cfg(feature = "std")] #[derive(Debug)] pub enum CompressProgramError { + /// Json serialization error when serializing the contract ABI. Json(JsonError), + /// Gzip encoding error. Io(std::io::Error), } + /// Json serialization error represented as a string message. This is done instead of exposing + /// `serde_json` error types to avoid leaking implementation details and ease error handling. #[derive(Debug)] pub struct JsonError { pub(crate) message: String, } + /// The total bytecode length is different than the implied length from + /// `bytecode_segment_lengths`. #[derive(Debug)] pub struct BytecodeSegmentLengthMismatchError { + /// The total length implied by `bytecode_segment_lengths`. pub segment_length: usize, + /// The actual bytecode length. pub bytecode_length: usize, } + /// The bytecode segment is invalid as it's not consecutive with the prior segment. #[derive(Debug)] pub struct InvalidBytecodeSegmentError { + /// The expected program counter implied from a previous processed segment. pub visited_pc: u64, + /// The actual starting program counter as specified by the segment bytecode offset. pub segment_start: u64, } + /// The bytecode segments specified in `bytecode_segment_lengths` do not cover the full range of + /// bytecode. #[derive(Debug)] pub struct PcOutOfRangeError { + /// The first out-of-range program counter. pub pc: u64, } @@ -410,6 +549,7 @@ pub use errors::{ pub use errors::CompressProgramError; impl SierraClass { + /// Computes the class hash of the Sierra (Cairo 1) class. pub fn class_hash(&self) -> Result { // Technically we don't have to use the Pythonic JSON style here. Doing this just to align // with the official `cairo-lang` CLI. @@ -442,6 +582,22 @@ impl SierraClass { Ok(normalize_address(hasher.finalize())) } + /// Flattens the Sierra (Cairo 1) class by serializing the ABI using a Pythonic JSON + /// representation. The Pythoic JSON format is used for achieving the exact same output as that + /// of `cairo-lang`. + /// + /// The flattening is necessary for class declaration as the network does not deal with + /// structured contract ABI. Instead, any ABI is treated as an opaque string. + /// + /// Therefore, a process is needed for transforming a structured ABI into a string. This process + /// is called "flattening" in this library. The word is chosen instead of "serializing" as the + /// latter implies that there must be a way to "deserialize" the resulting string back to its + /// original, structured form. However, that would not be guaranteed. + /// + /// While it's true that the in *this* particular implementation, the string can *indeed* be + /// deserialized back to the original form, it's easy to imagine other flattening strategies + /// that are more destructive. In the future, this library will also allow custom flattening + /// implementations where the Pythoic style one presented here would merely be one of them. pub fn flatten(self) -> Result { let abi = to_string_pythonic(&self.abi).map_err(|err| JsonError { message: format!("{}", err), @@ -457,6 +613,7 @@ impl SierraClass { } impl FlattenedSierraClass { + /// Computes the class hash of the flattened Sierra (Cairo 1) class. pub fn class_hash(&self) -> Felt { let mut hasher = PoseidonHasher::new(); hasher.update(PREFIX_CONTRACT_CLASS_V0_1_0); @@ -481,6 +638,7 @@ impl FlattenedSierraClass { } impl CompiledClass { + /// Computes the class hash of the Cairo assembly (CASM) class. pub fn class_hash(&self) -> Result { let mut hasher = PoseidonHasher::new(); hasher.update(PREFIX_COMPILED_CLASS_V1); diff --git a/starknet-core/src/types/eth_address.rs b/starknet-core/src/types/eth_address.rs index 5bfa95bd..fccda237 100644 --- a/starknet-core/src/types/eth_address.rs +++ b/starknet-core/src/types/eth_address.rs @@ -12,6 +12,7 @@ const MAX_L1_ADDRESS: Felt = Felt::from_raw([ 18406070939574861858, ]); +/// Ethereum address represented with a 20-byte array. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EthAddress { inner: [u8; 20], @@ -22,15 +23,20 @@ struct EthAddressVisitor; mod errors { use core::fmt::{Display, Formatter, Result}; + /// Errors parsing [`EthAddress`] from a hex string. #[derive(Debug)] pub enum FromHexError { + /// The hex string is not 40 hexadecimal characters in length without the `0x` prefix. UnexpectedLength, + /// The string contains non-hexadecimal characters. InvalidHexString, } + /// The [`Felt`] value is out of range for converting into [`EthAddress`]. #[derive(Debug)] pub struct FromFieldElementError; + /// The byte slice is out of range for converting into [`EthAddress`]. #[derive(Debug)] pub struct FromBytesSliceError; @@ -71,18 +77,22 @@ mod errors { pub use errors::{FromBytesSliceError, FromFieldElementError, FromHexError}; impl EthAddress { + /// Constructs [`EthAddress`] from a byte array. pub const fn from_bytes(bytes: [u8; 20]) -> Self { Self { inner: bytes } } + /// Parses [`EthAddress`] from a hex string. pub fn from_hex(hex: &str) -> Result { hex.parse() } + /// Constructs [`EthAddress`] from a [`Felt`]. pub fn from_felt(felt: &Felt) -> Result { felt.try_into() } + /// Gets a reference to the underlying byte array. pub const fn as_bytes(&self) -> &[u8; 20] { &self.inner } diff --git a/starknet-core/src/types/execution_result.rs b/starknet-core/src/types/execution_result.rs index b0d56a1c..fe2f83c1 100644 --- a/starknet-core/src/types/execution_result.rs +++ b/starknet-core/src/types/execution_result.rs @@ -4,14 +4,24 @@ use serde::{Deserialize, Serialize}; use super::TransactionExecutionStatus; -/// A more idiomatic way to access `execution_status` and `revert_reason`. +/// Execution result of a transaction. +/// +/// This struct ccorresponds to the `execution_status` and `revert_reason` fields of a transaction +/// receipt, capturing the fact that the presence of `revert_reason` depends on `execution_status`, +/// allowing more idiomatic access to `revert_reason`. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ExecutionResult { + /// The execution succeeded. Succeeded, - Reverted { reason: String }, + /// The execution reverted. + Reverted { + /// The reason that the execution was reverted. + reason: String, + }, } impl ExecutionResult { + /// Gets the [`TransactionExecutionStatus`]. pub const fn status(&self) -> TransactionExecutionStatus { match self { Self::Succeeded => TransactionExecutionStatus::Succeeded, diff --git a/starknet-core/src/types/hash_256.rs b/starknet-core/src/types/hash_256.rs index b6ad9061..f47be680 100644 --- a/starknet-core/src/types/hash_256.rs +++ b/starknet-core/src/types/hash_256.rs @@ -9,6 +9,7 @@ use starknet_types_core::felt::Felt; const HASH_256_BYTE_COUNT: usize = 32; +/// A 256-bit cryptographic hash. #[derive(Clone, Copy, PartialEq, Eq)] pub struct Hash256 { inner: [u8; HASH_256_BYTE_COUNT], @@ -19,12 +20,16 @@ struct Hash256Visitor; mod errors { use core::fmt::{Display, Formatter, Result}; + /// Errors parsing [`Hash256`] from a hex string. #[derive(Debug)] pub enum FromHexError { + /// The hex string is not 64 hexadecimal characters in length without the `0x` prefix. UnexpectedLength, + /// The string contains non-hexadecimal characters. InvalidHexString, } + /// The hash value is out of range for converting into [`Felt`]. #[derive(Debug)] pub struct ToFieldElementError; @@ -56,18 +61,22 @@ mod errors { pub use errors::{FromHexError, ToFieldElementError}; impl Hash256 { + /// Constructs [`Hash256`] from a byte array. pub const fn from_bytes(bytes: [u8; HASH_256_BYTE_COUNT]) -> Self { Self { inner: bytes } } + /// Parses [`Hash256`] from a hex string. pub fn from_hex(hex: &str) -> Result { hex.parse() } + /// Constructs [`Hash256`] from a [`Felt`]. pub fn from_felt(felt: &Felt) -> Self { felt.into() } + /// Gets a reference to the underlying byte array. pub const fn as_bytes(&self) -> &[u8; HASH_256_BYTE_COUNT] { &self.inner } diff --git a/starknet-core/src/types/mod.rs b/starknet-core/src/types/mod.rs index 0d5321f8..3e7756b4 100644 --- a/starknet-core/src/types/mod.rs +++ b/starknet-core/src/types/mod.rs @@ -41,12 +41,15 @@ pub use codegen::{ TransactionReceiptWithBlockInfo, TransactionTraceWithHash, TransactionWithReceipt, }; +/// Module containing the [`U256`] type. pub mod u256; pub use u256::U256; +/// Module containing the [`EthAddress`] type. pub mod eth_address; pub use eth_address::EthAddress; +/// Module containing the [`Hash256`] type. pub mod hash_256; pub use hash_256::Hash256; @@ -60,53 +63,85 @@ mod msg; pub use msg::MsgToL2; // TODO: move generated request code to `starknet-providers` +/// Module containing JSON-RPC request types. pub mod requests; +/// Module containing types related to Starknet contracts/classes. pub mod contract; pub use contract::ContractArtifact; +/// A block with transaction hashes that may or may not be pending. +/// +/// A pending block lacks certain information on the block header compared to a non-pending block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingBlockWithTxHashes { + /// A confirmed, non-pending block. Block(BlockWithTxHashes), + /// A pending block. PendingBlock(PendingBlockWithTxHashes), } +/// A block with full transactions that may or may not be pending. +/// +/// A pending block lacks certain information on the block header compared to a non-pending block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingBlockWithTxs { + /// A confirmed, non-pending block. Block(BlockWithTxs), + /// A pending block. PendingBlock(PendingBlockWithTxs), } +/// A block with full transactions and receipts that may or may not be pending. +/// +/// A pending block lacks certain information on the block header compared to a non-pending block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingBlockWithReceipts { + /// A confirmed, non-pending block. Block(BlockWithReceipts), + /// A pending block. PendingBlock(PendingBlockWithReceipts), } +/// State update of a block that may or may not be pending. +/// +/// State update for a pending block lacks certain information compared to that of a non-pending +/// block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingStateUpdate { + /// The state update is for a confirmed, non-pending block. Update(StateUpdate), + /// The state update is for a pending block. PendingUpdate(PendingStateUpdate), } +/// The hash and number (height) for a block. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BlockHashAndNumber { + /// The block's hash. #[serde_as(as = "UfeHex")] pub block_hash: Felt, + /// The block's number (height). pub block_number: u64, } +/// A Starknet client node's synchronization status. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyncStatusType { + /// The node is synchronizing. Syncing(SyncStatus), + /// The node is not synchronizing. NotSyncing, } +/// A "page" of events in a cursor-based pagniation system. +/// +/// This type is usually returned from the `starknet_getEvents` RPC method. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EventsPage { /// Matching events @@ -118,6 +153,7 @@ pub struct EventsPage { pub continuation_token: Option, } +/// Response for broadcasting an `INVOKE` transaction. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct InvokeTransactionResult { @@ -126,6 +162,7 @@ pub struct InvokeTransactionResult { pub transaction_hash: Felt, } +/// Response for broadcasting a `DECLARE` transaction. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeclareTransactionResult { @@ -137,6 +174,10 @@ pub struct DeclareTransactionResult { pub class_hash: Felt, } +/// Response for broadcasting a `DEPLOY` transaction. +/// +/// Note that `DEPLOY` transactions have been deprecated and disabled on all public Starknet +/// networks. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeployTransactionResult { @@ -148,6 +189,7 @@ pub struct DeployTransactionResult { pub contract_address: Felt, } +/// Response for broadcasting a `DEPLOY_ACCOUNT` transaction. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeployAccountTransactionResult { @@ -159,18 +201,25 @@ pub struct DeployAccountTransactionResult { pub contract_address: Felt, } -/// Block hash, number or tag +/// Block identifier in the form of hash, number or tag. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BlockId { + /// Block hash. Hash(Felt), + /// Block number (height). Number(u64), + /// Block tag. Tag(BlockTag), } +/// A "processed" contract class representation that's circulated in the network. This is different +/// from the class representation of compiler output. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum ContractClass { + /// A "processed" Sierra (Cairo 1) class. Sierra(FlattenedSierraClass), + /// A "processed" legacy (Cairo 0) class. Legacy(CompressedLegacyContractClass), } @@ -209,136 +258,189 @@ impl TransactionStatus { } } +/// A Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum Transaction { + /// An `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(InvokeTransaction), + /// An `L1_HANDLER` transaction. #[serde(rename = "L1_HANDLER")] L1Handler(L1HandlerTransaction), + /// A `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(DeclareTransaction), + /// A `DEPLOY` transaction. #[serde(rename = "DEPLOY")] Deploy(DeployTransaction), + /// A `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransaction), } +/// A Starknet transaction in its "mempool" representation that's broadcast by a client. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum BroadcastedTransaction { + /// An `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(BroadcastedInvokeTransaction), + /// A `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(BroadcastedDeclareTransaction), + /// A `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(BroadcastedDeployAccountTransaction), } +/// An `INVOKE` Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "version")] pub enum InvokeTransaction { + /// Version 0 `INVOKE` transaction. #[serde(rename = "0x0")] V0(InvokeTransactionV0), + /// Version 1 `INVOKE` transaction. #[serde(rename = "0x1")] V1(InvokeTransactionV1), + /// Version 3 `INVOKE` transaction. #[serde(rename = "0x3")] V3(InvokeTransactionV3), } +/// A `DECLARE` Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "version")] pub enum DeclareTransaction { + /// Version 0 `DECLARE` transaction. #[serde(rename = "0x0")] V0(DeclareTransactionV0), + /// Version 1 `DECLARE` transaction. #[serde(rename = "0x1")] V1(DeclareTransactionV1), + /// Version 2 `DECLARE` transaction. #[serde(rename = "0x2")] V2(DeclareTransactionV2), + /// Version 3 `DECLARE` transaction. #[serde(rename = "0x3")] V3(DeclareTransactionV3), } +/// A `DEPLOY_ACCOUNT` Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "version")] pub enum DeployAccountTransaction { + /// Version 1 `DEPLOY_ACCOUNT` transaction. #[serde(rename = "0x1")] V1(DeployAccountTransactionV1), + /// Version 3 `DEPLOY_ACCOUNT` transaction. #[serde(rename = "0x3")] V3(DeployAccountTransactionV3), } +/// An `INVOKE` Starknet transaction in its "mempool" representation. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum BroadcastedInvokeTransaction { + /// Version 1 `INVOKE` transaction. V1(BroadcastedInvokeTransactionV1), + /// Version 3 `INVOKE` transaction. V3(BroadcastedInvokeTransactionV3), } +/// A `DECLARE` Starknet transaction in its "mempool" representation. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum BroadcastedDeclareTransaction { + /// Version 1 `DECLARE` transaction. V1(BroadcastedDeclareTransactionV1), + /// Version 2 `DECLARE` transaction. V2(BroadcastedDeclareTransactionV2), + /// Version 3 `DECLARE` transaction. V3(BroadcastedDeclareTransactionV3), } +/// A `DEPLOY_ACCOUNT` Starknet transaction in its "mempool" representation. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum BroadcastedDeployAccountTransaction { + /// Version 1 `DEPLOY_ACCOUNT` transaction. V1(BroadcastedDeployAccountTransactionV1), + /// Version 3 `DEPLOY_ACCOUNT` transaction. V3(BroadcastedDeployAccountTransactionV3), } +/// Starknet transaction receipt containing execution results. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum TransactionReceipt { + /// Receipt for an `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(InvokeTransactionReceipt), + /// Receipt for an `L1_HANDLER` transaction. #[serde(rename = "L1_HANDLER")] L1Handler(L1HandlerTransactionReceipt), + /// Receipt for a `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(DeclareTransactionReceipt), + /// Receipt for a `DEPLOY` transaction. #[serde(rename = "DEPLOY")] Deploy(DeployTransactionReceipt), + /// Receipt for a `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransactionReceipt), } +/// ABI entry item for legacy (Cairo 0) contract classes. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum LegacyContractAbiEntry { + /// ABI entry representing a Cairo function. Function(LegacyFunctionAbiEntry), + /// ABI entry representing a Starknet event. Event(LegacyEventAbiEntry), + /// ABI entry representing a Cairo struct. Struct(LegacyStructAbiEntry), } +/// Execution trace of a Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum TransactionTrace { + /// Trace for an `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(InvokeTransactionTrace), + /// Trace for a `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransactionTrace), + /// Trace for an `L1_HANDLER` transaction. #[serde(rename = "L1_HANDLER")] L1Handler(L1HandlerTransactionTrace), + /// Trace for a `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(DeclareTransactionTrace), } +/// The execution result of a function invocation. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum ExecuteInvocation { + /// Successful invocation. Success(FunctionInvocation), + /// Failed and reverted invocation. Reverted(RevertedInvocation), } mod errors { use core::fmt::{Display, Formatter, Result}; + /// Errors parsing an L1-to-L2 message from transaction calldata. #[derive(Debug, PartialEq, Eq)] pub enum ParseMsgToL2Error { + /// The transaction calldata is empty. EmptyCalldata, + /// The L1 sender address is longer than 20 bytes. FromAddressOutOfRange, } @@ -364,6 +466,7 @@ mod errors { pub use errors::ParseMsgToL2Error; impl MaybePendingBlockWithTxHashes { + /// Gets a reference to the list of transaction hashes. pub fn transactions(&self) -> &[Felt] { match self { Self::Block(block) => &block.transactions, @@ -371,6 +474,7 @@ impl MaybePendingBlockWithTxHashes { } } + /// Gets a reference to the L1 gas price. pub const fn l1_gas_price(&self) -> &ResourcePrice { match self { Self::Block(block) => &block.l1_gas_price, @@ -380,6 +484,7 @@ impl MaybePendingBlockWithTxHashes { } impl MaybePendingBlockWithTxs { + /// Gets a reference to the list of transactions. pub fn transactions(&self) -> &[Transaction] { match self { Self::Block(block) => &block.transactions, @@ -387,6 +492,7 @@ impl MaybePendingBlockWithTxs { } } + /// Gets a reference to the L1 gas price. pub const fn l1_gas_price(&self) -> &ResourcePrice { match self { Self::Block(block) => &block.l1_gas_price, @@ -396,6 +502,7 @@ impl MaybePendingBlockWithTxs { } impl MaybePendingBlockWithReceipts { + /// Gets a reference to the list of transactions with receipts. pub fn transactions(&self) -> &[TransactionWithReceipt] { match self { Self::Block(block) => &block.transactions, @@ -403,6 +510,7 @@ impl MaybePendingBlockWithReceipts { } } + /// Gets a reference to the L1 gas price. pub const fn l1_gas_price(&self) -> &ResourcePrice { match self { Self::Block(block) => &block.l1_gas_price, @@ -412,6 +520,7 @@ impl MaybePendingBlockWithReceipts { } impl TransactionStatus { + /// Gets a reference to the transaction's finality status. pub const fn finality_status(&self) -> SequencerTransactionStatus { match self { Self::Received => SequencerTransactionStatus::Received, @@ -423,6 +532,7 @@ impl TransactionStatus { } impl Transaction { + /// Gets a reference to the transaction's hash. pub const fn transaction_hash(&self) -> &Felt { match self { Self::Invoke(tx) => tx.transaction_hash(), @@ -435,6 +545,7 @@ impl Transaction { } impl InvokeTransaction { + /// Gets a reference to the transaction's hash. pub const fn transaction_hash(&self) -> &Felt { match self { Self::V0(tx) => &tx.transaction_hash, @@ -445,6 +556,7 @@ impl InvokeTransaction { } impl DeclareTransaction { + /// Gets a reference to the transaction's hash. pub const fn transaction_hash(&self) -> &Felt { match self { Self::V0(tx) => &tx.transaction_hash, @@ -456,6 +568,7 @@ impl DeclareTransaction { } impl DeployAccountTransaction { + /// Gets a reference to the transaction's hash. pub const fn transaction_hash(&self) -> &Felt { match self { Self::V1(tx) => &tx.transaction_hash, @@ -465,6 +578,7 @@ impl DeployAccountTransaction { } impl TransactionReceipt { + /// Gets a reference to the transaction's hash. pub const fn transaction_hash(&self) -> &Felt { match self { Self::Invoke(receipt) => &receipt.transaction_hash, @@ -475,6 +589,7 @@ impl TransactionReceipt { } } + /// Gets a reference to the transaction's finality status. pub const fn finality_status(&self) -> &TransactionFinalityStatus { match self { Self::Invoke(receipt) => &receipt.finality_status, @@ -485,6 +600,7 @@ impl TransactionReceipt { } } + /// Gets a reference to the transaction's execution result. pub const fn execution_result(&self) -> &ExecutionResult { match self { Self::Invoke(receipt) => &receipt.execution_result, @@ -497,6 +613,8 @@ impl TransactionReceipt { } impl L1HandlerTransaction { + /// Parses [`MsgToL2`] from the transaction's calldata. This should not never fail on a genuine + /// `L1_HANDLER` transaction. pub fn parse_msg_to_l2(&self) -> Result { self.calldata.split_first().map_or( Err(ParseMsgToL2Error::EmptyCalldata), diff --git a/starknet-core/src/types/msg.rs b/starknet-core/src/types/msg.rs index c34919e9..beec076a 100644 --- a/starknet-core/src/types/msg.rs +++ b/starknet-core/src/types/msg.rs @@ -5,12 +5,18 @@ use starknet_types_core::felt::Felt; use super::{EthAddress, Hash256, MsgToL1}; +/// An L1-to-L2 message sent from Ethereum to Starknet. #[derive(Debug, Clone)] pub struct MsgToL2 { + /// The Ethereum address sending the message. pub from_address: EthAddress, + /// The Starknet contract address that handles the message. pub to_address: Felt, + /// The entrypoint selector on the handler contract. pub selector: Felt, + /// The calldata to be used for the handler contract invocation. pub payload: Vec, + /// The nonce on the message for duduplication. pub nonce: u64, } diff --git a/starknet-core/src/types/receipt_block.rs b/starknet-core/src/types/receipt_block.rs index a6216dfe..62e611f2 100644 --- a/starknet-core/src/types/receipt_block.rs +++ b/starknet-core/src/types/receipt_block.rs @@ -4,11 +4,22 @@ use serde_with::serde_as; use crate::serde::unsigned_field_element::UfeHex; use starknet_types_core::felt::Felt; -/// A more idiomatic way to access `execution_status` and `revert_reason`. +/// Block identifier used in [`TransactionReceiptWithBlockInfo`]. +/// +/// Instead of directly exposing the `block_hash` and `block_number` fields as [`Option`], +/// this struct captures the fact that these fields are always [`Some`](Option::Some) or +/// [`None`](Option::None) toggether, allowing idiomatic access without unnecessary unwraps. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReceiptBlock { + /// The receipt is attached to a pending block. Pending, - Block { block_hash: Felt, block_number: u64 }, + /// The receipt is attached to a confirmed, non-pending block. + Block { + /// Block hash. + block_hash: Felt, + /// Block number (height). + block_number: u64, + }, } impl ReceiptBlock { diff --git a/starknet-core/src/types/u256.rs b/starknet-core/src/types/u256.rs index f02b87c0..fe57e728 100644 --- a/starknet-core/src/types/u256.rs +++ b/starknet-core/src/types/u256.rs @@ -11,35 +11,41 @@ use crate::types::Felt; pub struct U256(crypto_bigint::U256); impl U256 { - #[cfg(target_pointer_width = "64")] + /// Constructs a [U256] from the low 128 bits and the high 128 bits, similar to how they're + /// represented in Cairo. pub const fn from_words(low: u128, high: u128) -> Self { - Self(crypto_bigint::U256::from_words([ - low as u64, - (low >> 64) as u64, - high as u64, - (high >> 64) as u64, - ])) - } + #[cfg(target_pointer_width = "64")] + { + Self(crypto_bigint::U256::from_words([ + low as u64, + (low >> 64) as u64, + high as u64, + (high >> 64) as u64, + ])) + } - #[cfg(target_pointer_width = "32")] - pub const fn from_words(low: u128, high: u128) -> Self { - Self(crypto_bigint::U256::from_words([ - low as u32, - (low >> 32) as u32, - (low >> 64) as u32, - (low >> 96) as u32, - high as u32, - (high >> 32) as u32, - (high >> 64) as u32, - (high >> 96) as u32, - ])) + #[cfg(target_pointer_width = "32")] + { + Self(crypto_bigint::U256::from_words([ + low as u32, + (low >> 32) as u32, + (low >> 64) as u32, + (low >> 96) as u32, + high as u32, + (high >> 32) as u32, + (high >> 64) as u32, + (high >> 96) as u32, + ])) + } } + /// Gets the lower (least significant) 128 bits as [u128]. pub const fn low(&self) -> u128 { let words = u256_to_u64_array(&self.0); words[0] as u128 + ((words[1] as u128) << 64) } + /// Gets the higher (most significant) 128 bits as [u128]. pub const fn high(&self) -> u128 { let words = u256_to_u64_array(&self.0); words[2] as u128 + ((words[3] as u128) << 64) diff --git a/starknet-core/src/utils.rs b/starknet-core/src/utils.rs index 10bac139..00da1135 100644 --- a/starknet-core/src/utils.rs +++ b/starknet-core/src/utils.rs @@ -29,31 +29,46 @@ const CONTRACT_ADDRESS_PREFIX: Felt = Felt::from_raw([ /// The uniqueness settings for UDC deployments. #[derive(Debug, Clone)] pub enum UdcUniqueness { + /// Contract deployment is not unique to the deployer, as in any deployer account can deploy to + /// this same deployed address given the same settings. NotUnique, + /// Contract deployment is unique to the deployer, as in the deployer address is used to form + /// part of the deployment salt, making it impossible to another deployer account to deploy to + /// the same deployed address, even using the same UDC inputs. Unique(UdcUniqueSettings), } +/// The uniqueness settings when using [`UdcUniqueness::Unique`] for contract deployment. #[derive(Debug, Clone)] pub struct UdcUniqueSettings { + /// Contract address of the deployer account, which is the caller of the UDC. pub deployer_address: Felt, + /// The UDC address. pub udc_contract_address: Felt, } mod errors { use core::fmt::{Display, Formatter, Result}; + /// The string provided contains non-ASCII characters. #[derive(Debug)] pub struct NonAsciiNameError; + /// Possible errors for encoding a Cairo short string. #[derive(Debug)] pub enum CairoShortStringToFeltError { + /// The string provided contains non-ASCII characters. NonAsciiCharacter, + /// The string provided is longer than 31 characters. StringTooLong, } + /// Possible errors for decoding a Cairo short string. #[derive(Debug)] pub enum ParseCairoShortStringError { + /// The encoded [`Felt`] value is out of range. ValueOutOfRange, + /// A null terminator (`0x00`) is encountered. UnexpectedNullTerminator, } @@ -96,7 +111,8 @@ mod errors { } pub use errors::{CairoShortStringToFeltError, NonAsciiNameError, ParseCairoShortStringError}; -/// A variant of eth-keccak that computes a value that fits in a Starknet field element. +/// A variant of eth-keccak that computes a value that fits in a Starknet field element. It performs +/// a standard Keccak-256 but with the 6 most significant bits removed. pub fn starknet_keccak(data: &[u8]) -> Felt { let mut hasher = Keccak256::new(); hasher.update(data); @@ -109,6 +125,11 @@ pub fn starknet_keccak(data: &[u8]) -> Felt { Felt::from_bytes_be(unsafe { &*(hash[..].as_ptr() as *const [u8; 32]) }) } +/// Calculates the entrypoint selector from a human-readable function name. +/// +/// Returns the [Starknet Keccak](fn.starknet_keccak) of the function name in most cases, except for +/// 2 special built-in default entrypoints of `__default__` and `__l1_default__` for which `0` is +/// returned instead. pub fn get_selector_from_name(func_name: &str) -> Result { if func_name == DEFAULT_ENTRY_POINT_NAME || func_name == DEFAULT_L1_ENTRY_POINT_NAME { Ok(Felt::ZERO) @@ -122,6 +143,11 @@ pub fn get_selector_from_name(func_name: &str) -> Result Result { let var_name_bytes = var_name.as_bytes(); if var_name_bytes.is_ascii() { @@ -178,8 +204,13 @@ pub fn parse_cairo_short_string(felt: &Felt) -> Result Felt { address.mod_floor(&ADDR_BOUND) }