Skip to content

Commit

Permalink
feat(rpc): add starknet_getCompiledCasm
Browse files Browse the repository at this point in the history
  • Loading branch information
t00ts committed Nov 5, 2024
1 parent 29f93d0 commit 18f644a
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ bitvec = { workspace = true }
fake = { workspace = true, features = ["derive"] }
metrics = { workspace = true }
num-bigint = { workspace = true }
num-traits = "0.2"
paste = { workspace = true }
pathfinder-crypto = { path = "../crypto" }
primitive-types = { workspace = true, features = ["serde"] }
Expand Down
103 changes: 103 additions & 0 deletions crates/common/src/casm_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use num_bigint::BigUint;
use num_traits::Num;
use serde::{Deserialize, Serialize};

/// A contract in the Starknet network.
#[derive(Debug, Serialize, Deserialize)]
pub struct CasmContractClass {
pub bytecode: Vec<BigUintAsHex>,
pub bytecode_segment_lengths: Option<NestedIntList>,
pub compiler_version: String,
pub hints: serde_json::Value,
pub entry_points_by_type: CasmContractEntryPoints,
#[serde(
serialize_with = "serialize_big_uint",
deserialize_with = "deserialize_big_uint"
)]
pub prime: BigUint,
}

/// The entry points (functions) of a contract.
#[derive(Debug, Serialize, Deserialize)]
pub struct CasmContractEntryPoints {
#[serde(rename = "EXTERNAL")]
pub external: Vec<CasmContractEntryPoint>,
#[serde(rename = "L1_HANDLER")]
pub l1_handler: Vec<CasmContractEntryPoint>,
#[serde(rename = "CONSTRUCTOR")]
pub constructor: Vec<CasmContractEntryPoint>,
}

/// An entry point (function) of a contract.
#[derive(Debug, Serialize, Deserialize)]
pub struct CasmContractEntryPoint {
/// A field element that encodes the signature of the called function.
#[serde(
serialize_with = "serialize_big_uint",
deserialize_with = "deserialize_big_uint"
)]
pub selector: BigUint,
/// The offset of the instruction that should be called within the contract
/// bytecode.
pub offset: usize,
// List of builtins.
pub builtins: Vec<String>,
}

/// A field element that encodes the signature of the called function.
#[derive(Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct BigUintAsHex {
/// A field element that encodes the signature of the called function.
#[serde(
serialize_with = "serialize_big_uint",
deserialize_with = "deserialize_big_uint"
)]
pub value: BigUint,
}

pub fn serialize_big_uint<S>(num: &BigUint, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{num:#x}"))
}

pub fn deserialize_big_uint<'a, D>(deserializer: D) -> Result<BigUint, D::Error>
where
D: serde::Deserializer<'a>,
{
let s = &<String as serde::Deserialize>::deserialize(deserializer)?;
match s.strip_prefix("0x") {
Some(num_no_prefix) => BigUint::from_str_radix(num_no_prefix, 16)
.map_err(|error| serde::de::Error::custom(format!("{error}"))),
None => Err(serde::de::Error::custom(format!(
"{s} does not start with `0x` is missing."
))),
}
}

/// NestedIntList is either a list of NestedIntList or an integer.
/// E.g., `[0, [1, 2], [3, [4]]]`.
///
/// Used to represents the lengths of the segments in a contract, which are in a
/// form of a tree.
///
/// For example, the contract may be segmented by functions, where each function
/// is segmented by its branches. It is also possible to have the inner
/// segmentation only for some of the functions, while others are kept as
/// non-segmented leaves in the tree.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum NestedIntList {
Leaf(usize),
Node(Vec<NestedIntList>),
}

impl TryFrom<&str> for CasmContractClass {
type Error = serde_json::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
serde_json::from_str(value)
}
}
1 change: 1 addition & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use pathfinder_crypto::Felt;
use primitive_types::H160;
use serde::{Deserialize, Serialize};

pub mod casm_class;
pub mod class_definition;
pub mod consts;
pub mod error;
Expand Down
4 changes: 1 addition & 3 deletions crates/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,9 +908,7 @@ mod tests {
])]
#[case::v0_8_trace("/rpc/v0_8", "v08/starknet_trace_api_openrpc.json", &[])]
#[case::v0_8_write("/rpc/v0_8", "v08/starknet_write_api.json", &[])]
#[case::v0_8_executables("/rpc/v0_8", "v08/starknet_executables.json", &[
"starknet_getCompiledCasm",
])]

// get_transaction_status is now part of the official spec, so we are phasing it out.
#[case::v0_8_pathfinder("/rpc/v0_8", "pathfinder_rpc_api.json", &["pathfinder_version", "pathfinder_getTransactionStatus"])]

Expand Down
2 changes: 2 additions & 0 deletions crates/rpc/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod get_block_with_txs;
pub mod get_class;
pub mod get_class_at;
pub mod get_class_hash_at;
pub mod get_compiled_casm;
pub mod get_events;
pub mod get_messages_status;
pub mod get_nonce;
Expand Down Expand Up @@ -49,6 +50,7 @@ pub use get_block_with_txs::get_block_with_txs;
pub use get_class::get_class;
pub use get_class_at::get_class_at;
pub use get_class_hash_at::get_class_hash_at;
pub use get_compiled_casm::get_compiled_casm;
pub use get_events::get_events;
pub use get_messages_status::get_messages_status;
pub use get_nonce::get_nonce;
Expand Down
96 changes: 96 additions & 0 deletions crates/rpc/src/method/get_compiled_casm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use anyhow::Context;
use pathfinder_common::casm_class::CasmContractClass;
use pathfinder_common::ClassHash;

use crate::context::RpcContext;
use crate::error::ApplicationError;

#[derive(Debug)]
pub struct Input {
pub class_hash: ClassHash,
}

impl crate::dto::DeserializeForVersion for Input {
fn deserialize(value: crate::dto::Value) -> Result<Self, serde_json::Error> {
value.deserialize_map(|value| {
Ok(Self {
class_hash: ClassHash(value.deserialize("class_hash")?),
})
})
}
}

#[derive(Debug)]
pub struct Output(CasmContractClass);

impl crate::dto::serialize::SerializeForVersion for Output {
fn serialize(
&self,
serializer: crate::dto::serialize::Serializer,
) -> Result<crate::dto::serialize::Ok, crate::dto::serialize::Error> {
self.0.serialize(serializer)
}
}

#[derive(Debug)]
pub enum Error {
CompilationFailed,
ClassHashNotFound(ClassHash),
Internal(anyhow::Error),
}

impl From<anyhow::Error> for Error {
fn from(error: anyhow::Error) -> Self {
Self::Internal(error)
}
}

impl From<Error> for crate::jsonrpc::RpcError {
fn from(error: Error) -> Self {
match error {
Error::CompilationFailed => Self::ApplicationError(ApplicationError::CompilationFailed),
Error::ClassHashNotFound(_) => {
Self::ApplicationError(ApplicationError::ClassHashNotFound)
}
Error::Internal(e) => Self::InternalError(e),
}
}
}

/// Get the compiled casm for a given class hash.
pub async fn get_compiled_casm(context: RpcContext, input: Input) -> Result<Output, Error> {
let span = tracing::Span::current();
let jh = tokio::task::spawn_blocking(move || -> Result<Output, Error> {
let _g = span.enter();

let mut db = context
.storage
.connection()
.context("Opening database connection")
.map_err(Error::Internal)?;

let tx = db
.transaction()
.context("Creating database transaction")
.map_err(Error::Internal)?;

// Get the class definition
let casm_definition = tx
.casm_definition(input.class_hash)
.context("Fetching class definition")
.map_err(Error::Internal)?
.ok_or(Error::ClassHashNotFound(input.class_hash))?;

// Convert to JSON string
let casm_definition_str = String::from_utf8_lossy(&casm_definition);

// Parse the casm definition
let casm_contract_class = CasmContractClass::try_from(casm_definition_str.as_ref())
.context("Parsing casm definition")
.map_err(|_| Error::CompilationFailed)?;

Ok(Output(casm_contract_class))
});

jh.await.context("Fetching compiled casm")?
}
2 changes: 1 addition & 1 deletion crates/rpc/src/v08.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ pub fn register_routes() -> RpcRouterBuilder {
.register("starknet_syncing", crate::method::syncing)
.register("starknet_traceBlockTransactions", crate::method::trace_block_transactions)
.register("starknet_traceTransaction", crate::method::trace_transaction)

.register("starknet_getCompiledCasm", crate::method::get_compiled_casm)
.register("pathfinder_getProof", crate::pathfinder::methods::get_proof)
}

0 comments on commit 18f644a

Please sign in to comment.