Skip to content

Commit de73a3d

Browse files
kariychudkowsky
authored andcommitted
feat: preserve class abi (#356)
the `abi` field in rpc format for the contract class can be an arbitrary string. this is what the jsonrps [spec] says: ```json "abi": { "title": "ABI", "type": "string", "description": "The class ABI, as supplied by the user declaring the class" } ``` currently, if the rpc abi string can't be converted into the `ContractAbi` type, it will fail during the deserialization phase. the abi string must be preserved as well because it's part of the class hash computation. so we need to preserve the raw string even if it's just some random string. Starknet class with arbitrary abi string: `0x22b7d06d355ddb1c485ec9cc09b488e77e63318db8833104aed56d598416417` where the abi string returned by the RPC is literally this `"Declared from starknet-rs test case. Timestamp (ms): 1709974545243"`. superseded #353 . this change also include a database version bump ( 7 -> 8 ) because of the change made to the value type of the `Classes` table i.e., the `katana_primitives::class::ContractClass` enum. [spec]: https://github.com/starkware-libs/starknet-specs/blob/c2e93098b9c2ca0423b7f4d15b201f52f22d8c36/api/starknet_api_openrpc.json#L3215C8-L3219C12
1 parent 2abf374 commit de73a3d

File tree

22 files changed

+357
-126
lines changed

22 files changed

+357
-126
lines changed

Cargo.lock

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/executor/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ thiserror.workspace = true
1919
tracing.workspace = true
2020

2121
# cairo-native
22+
cairo-lang-starknet-classes = { workspace = true, optional = true }
2223
cairo-native = { version = "0.4.1", optional = true }
2324
cairo-vm.workspace = true
2425
parking_lot.workspace = true
@@ -49,7 +50,7 @@ pprof.workspace = true
4950
rayon.workspace = true
5051

5152
[features]
52-
native = [ "blockifier/cairo_native", "dep:cairo-native", "dep:rayon" ]
53+
native = [ "blockifier/cairo_native", "dep:cairo-native", "dep:rayon", "dep:cairo-lang-starknet-classes" ]
5354

5455
[[bench]]
5556
harness = false

crates/executor/src/implementation/blockifier/cache.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,12 @@ impl ClassCache {
250250
use cairo_native::OptLevel;
251251

252252
#[cfg(feature = "native")]
253-
let program = sierra.extract_sierra_program().unwrap();
253+
let program = cairo_lang_starknet_classes::contract_class::ContractClass::from(
254+
sierra.clone(), // TODO: avoid cloning here
255+
)
256+
.extract_sierra_program()
257+
.unwrap();
258+
254259
#[cfg(feature = "native")]
255260
let entry_points = sierra.entry_points_by_type.clone();
256261

crates/gateway/gateway-types/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use katana_primitives::class::{ClassHash, CompiledClassHash};
2727
use katana_primitives::contract::{Nonce, StorageKey, StorageValue};
2828
use katana_primitives::da::L1DataAvailabilityMode;
2929
use katana_primitives::{ContractAddress, Felt};
30-
pub use katana_rpc_types::class::SierraClass;
30+
pub use katana_rpc_types::class::RpcSierraContractClass;
3131
use serde::{Deserialize, Serialize};
3232
use starknet::core::types::ResourcePrice;
3333

crates/gateway/gateway-types/src/transaction.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use katana_primitives::transaction::{
2424
TxWithHash,
2525
};
2626
use katana_primitives::{ContractAddress, Felt};
27-
use katana_rpc_types::SierraClassAbi;
2827
use serde::{Deserialize, Deserializer, Serialize};
2928

3029
/// API response for an INVOKE_FUNCTION transaction
@@ -257,13 +256,15 @@ pub struct CompressedSierraClass {
257256
pub sierra_program: CompressedSierraProgram,
258257
pub contract_class_version: String,
259258
pub entry_points_by_type: ContractEntryPoints,
260-
pub abi: SierraClassAbi,
259+
pub abi: Option<String>,
261260
}
262261

263-
impl TryFrom<katana_rpc_types::class::SierraClass> for CompressedSierraClass {
262+
impl TryFrom<katana_rpc_types::class::RpcSierraContractClass> for CompressedSierraClass {
264263
type Error = CompressedSierraProgramError;
265264

266-
fn try_from(value: katana_rpc_types::class::SierraClass) -> Result<Self, Self::Error> {
265+
fn try_from(
266+
value: katana_rpc_types::class::RpcSierraContractClass,
267+
) -> Result<Self, Self::Error> {
267268
let abi = value.abi;
268269
let entry_points_by_type = value.entry_points_by_type;
269270
let contract_class_version = value.contract_class_version;
@@ -272,7 +273,7 @@ impl TryFrom<katana_rpc_types::class::SierraClass> for CompressedSierraClass {
272273
}
273274
}
274275

275-
impl TryFrom<CompressedSierraClass> for katana_rpc_types::class::SierraClass {
276+
impl TryFrom<CompressedSierraClass> for katana_rpc_types::class::RpcSierraContractClass {
276277
type Error = CompressedSierraProgramError;
277278

278279
fn try_from(value: CompressedSierraClass) -> Result<Self, Self::Error> {
@@ -883,7 +884,7 @@ mod tests {
883884

884885
use katana_primitives::fee::{ResourceBounds, ResourceBoundsMapping, Tip};
885886
use katana_primitives::{address, Felt};
886-
use katana_rpc_types::SierraClass;
887+
use katana_rpc_types::RpcSierraContractClass;
887888

888889
use super::*;
889890

@@ -931,7 +932,7 @@ mod tests {
931932

932933
#[test]
933934
fn test_conversion_from_rpc_query_declare_tx() {
934-
let sierra_class = Arc::new(katana_rpc_types::class::SierraClass {
935+
let sierra_class = Arc::new(katana_rpc_types::class::RpcSierraContractClass {
935936
sierra_program: vec![Felt::from(0x123), Felt::from(0x456)],
936937
contract_class_version: "0.1.0".to_string(),
937938
entry_points_by_type: Default::default(),
@@ -979,7 +980,7 @@ mod tests {
979980
assert_eq!(gateway_tx.fee_data_availability_mode, rpc_tx.fee_data_availability_mode.into());
980981

981982
// convert the gateway contract class to rpc contract class and ensure they are equal
982-
let converted_sierra_class: SierraClass =
983+
let converted_sierra_class: RpcSierraContractClass =
983984
gateway_tx.contract_class.as_ref().clone().try_into().unwrap();
984985
assert_eq!(converted_sierra_class, sierra_class.as_ref().clone());
985986
}

crates/primitives/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ version.workspace = true
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10+
cairo-vm.workspace = true
11+
cairo-lang-sierra.workspace = true
12+
cairo-lang-utils.workspace = true
13+
cairo-lang-starknet-classes.workspace = true
14+
1015
anyhow.workspace = true
1116
arbitrary = { workspace = true, optional = true }
1217
blockifier = { workspace = true, features = [ "testing" ] } # some Clone derives are gated behind 'testing' feature
1318
cainome-cairo-serde.workspace = true
14-
cairo-lang-starknet-classes.workspace = true
15-
cairo-vm.workspace = true
1619
derive_more.workspace = true
1720
heapless = { version = "0.8.0", features = [ "serde" ] }
1821
lazy_static.workspace = true

crates/primitives/src/class.rs

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::str::FromStr;
22

3-
use cairo_lang_starknet_classes::abi;
43
use cairo_lang_starknet_classes::casm_contract_class::StarknetSierraCompilationError;
54
use cairo_lang_starknet_classes::contract_class::{
65
version_id_from_serialized_sierra_program, ContractEntryPoint, ContractEntryPoints,
76
};
7+
use cairo_lang_utils::bigint::BigUintAsHex;
88
use serde_json_pythonic::to_string_pythonic;
99
use starknet::macros::short_string;
1010
use starknet_api::contract_class::SierraVersion;
@@ -18,8 +18,6 @@ pub type ClassHash = Felt;
1818
/// The hash of a compiled contract class.
1919
pub type CompiledClassHash = Felt;
2020

21-
/// The canonical contract class (Sierra) type.
22-
pub type SierraContractClass = cairo_lang_starknet_classes::contract_class::ContractClass;
2321
/// The canonical legacy class (Cairo 0) type.
2422
pub type LegacyContractClass = starknet_api::deprecated_contract_class::ContractClass;
2523

@@ -29,6 +27,97 @@ pub type CasmContractClass = cairo_lang_starknet_classes::casm_contract_class::C
2927
/// ABI for Sierra-based classes.
3028
pub type ContractAbi = cairo_lang_starknet_classes::abi::Contract;
3129

30+
#[derive(Debug, Clone, Eq, PartialEq)]
31+
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize), serde(untagged))]
32+
pub enum MaybeInvalidSierraContractAbi {
33+
Valid(ContractAbi),
34+
Invalid(String),
35+
}
36+
37+
impl std::fmt::Display for MaybeInvalidSierraContractAbi {
38+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39+
match self {
40+
MaybeInvalidSierraContractAbi::Valid(abi) => {
41+
let s = to_string_pythonic(abi).expect("failed to serialize abi");
42+
write!(f, "{}", s)
43+
}
44+
MaybeInvalidSierraContractAbi::Invalid(abi) => write!(f, "{}", abi),
45+
}
46+
}
47+
}
48+
49+
impl From<String> for MaybeInvalidSierraContractAbi {
50+
fn from(value: String) -> Self {
51+
match serde_json::from_str::<ContractAbi>(&value) {
52+
Ok(abi) => MaybeInvalidSierraContractAbi::Valid(abi),
53+
Err(..) => MaybeInvalidSierraContractAbi::Invalid(value),
54+
}
55+
}
56+
}
57+
58+
impl From<&str> for MaybeInvalidSierraContractAbi {
59+
fn from(value: &str) -> Self {
60+
match serde_json::from_str::<ContractAbi>(value) {
61+
Ok(abi) => MaybeInvalidSierraContractAbi::Valid(abi),
62+
Err(..) => MaybeInvalidSierraContractAbi::Invalid(value.to_string()),
63+
}
64+
}
65+
}
66+
67+
/// Represents a contract in the Starknet network.
68+
///
69+
/// The canonical contract class (Sierra) type.
70+
#[derive(Clone, Debug, PartialEq, Eq)]
71+
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
72+
pub struct SierraContractClass {
73+
pub sierra_program: Vec<BigUintAsHex>,
74+
pub sierra_program_debug_info: Option<cairo_lang_sierra::debug_info::DebugInfo>,
75+
pub contract_class_version: String,
76+
pub entry_points_by_type: ContractEntryPoints,
77+
pub abi: Option<MaybeInvalidSierraContractAbi>,
78+
}
79+
80+
impl SierraContractClass {
81+
/// Computes the hash of the Sierra contract class.
82+
pub fn hash(&self) -> ClassHash {
83+
let Self { sierra_program, abi, entry_points_by_type, .. } = self;
84+
85+
let program: Vec<Felt> = sierra_program.iter().map(|f| f.value.clone().into()).collect();
86+
let abi: String = abi.as_ref().map(|abi| abi.to_string()).unwrap_or_default();
87+
88+
compute_sierra_class_hash(&abi, entry_points_by_type, &program)
89+
}
90+
}
91+
92+
impl From<SierraContractClass> for cairo_lang_starknet_classes::contract_class::ContractClass {
93+
fn from(value: SierraContractClass) -> Self {
94+
let abi = value.abi.and_then(|abi| match abi {
95+
MaybeInvalidSierraContractAbi::Invalid(..) => None,
96+
MaybeInvalidSierraContractAbi::Valid(abi) => Some(abi),
97+
});
98+
99+
cairo_lang_starknet_classes::contract_class::ContractClass {
100+
abi,
101+
sierra_program: value.sierra_program,
102+
entry_points_by_type: value.entry_points_by_type,
103+
contract_class_version: value.contract_class_version,
104+
sierra_program_debug_info: value.sierra_program_debug_info,
105+
}
106+
}
107+
}
108+
109+
impl From<cairo_lang_starknet_classes::contract_class::ContractClass> for SierraContractClass {
110+
fn from(value: cairo_lang_starknet_classes::contract_class::ContractClass) -> Self {
111+
SierraContractClass {
112+
abi: value.abi.map(MaybeInvalidSierraContractAbi::Valid),
113+
sierra_program: value.sierra_program,
114+
entry_points_by_type: value.entry_points_by_type,
115+
contract_class_version: value.contract_class_version,
116+
sierra_program_debug_info: value.sierra_program_debug_info,
117+
}
118+
}
119+
}
120+
32121
#[derive(Debug, thiserror::Error)]
33122
pub enum ContractClassCompilationError {
34123
#[error(transparent)]
@@ -51,23 +140,7 @@ impl ContractClass {
51140
/// Computes the hash of the class.
52141
pub fn class_hash(&self) -> Result<ClassHash, ComputeClassHashError> {
53142
match self {
54-
Self::Class(class) => {
55-
// Technically we don't have to use the Pythonic JSON style here. Doing this just to
56-
// align with the official `cairo-lang` CLI.
57-
//
58-
// TODO: add an `AbiFormatter` trait and let users choose which one to use.
59-
let abi = class.abi.as_ref();
60-
let abi_str = to_string_pythonic(abi.unwrap_or(&abi::Contract::default())).unwrap();
61-
62-
let sierra_program = &class
63-
.sierra_program
64-
.iter()
65-
.map(|f| f.value.clone().into())
66-
.collect::<Vec<Felt>>();
67-
68-
Ok(compute_sierra_class_hash(&abi_str, &class.entry_points_by_type, sierra_program))
69-
}
70-
143+
Self::Class(class) => Ok(class.hash()),
71144
Self::Legacy(class) => compute_legacy_class_hash(class),
72145
}
73146
}
@@ -77,7 +150,7 @@ impl ContractClass {
77150
match self {
78151
Self::Legacy(class) => Ok(CompiledClass::Legacy(class)),
79152
Self::Class(class) => {
80-
let casm = CasmContractClass::from_contract_class(class, true, usize::MAX)?;
153+
let casm = CasmContractClass::from_contract_class(class.into(), true, usize::MAX)?;
81154
let casm = CompiledClass::Class(casm);
82155
Ok(casm)
83156
}

crates/rpc/rpc-server/tests/starknet.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,9 @@ async fn get_compiled_casm() {
166166
// Setup expected compiled class data to verify against
167167

168168
use katana_primitives::class::{ContractClass, SierraContractClass};
169-
use katana_rpc_types::SierraClass as RpcSierraClass;
169+
use katana_rpc_types::RpcSierraContractClass;
170170

171-
let rpc_class = RpcSierraClass::try_from(contract).unwrap();
171+
let rpc_class = RpcSierraContractClass::try_from(contract).unwrap();
172172
let class = SierraContractClass::try_from(rpc_class).unwrap();
173173
let expected_casm = ContractClass::Class(class).compile().unwrap();
174174

crates/rpc/rpc-types/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ katana-pool-api.workspace = true
1313
katana-trie.workspace = true
1414
serde-utils.workspace = true
1515

16-
derive_more.workspace = true
1716
cainome.workspace = true
1817
cainome-cairo-serde.workspace = true
1918
cairo-lang-starknet-classes.workspace = true

crates/rpc/rpc-types/src/broadcasted.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use katana_primitives::utils::get_contract_address;
1616
use katana_primitives::{ContractAddress, Felt};
1717
use serde::{de, Deserialize, Deserializer, Serialize};
1818

19-
use crate::class::SierraClass;
19+
use crate::class::RpcSierraContractClass;
2020

2121
pub const QUERY_VERSION_OFFSET: Felt =
2222
Felt::from_raw([576460752142434320, 18446744073709551584, 17407, 18446744073700081665]);
@@ -72,7 +72,7 @@ pub struct UntypedBroadcastedTx {
7272
pub compiled_class_hash: Option<CompiledClassHash>,
7373

7474
#[serde(default, skip_serializing_if = "Option::is_none")]
75-
pub contract_class: Option<Arc<SierraClass>>,
75+
pub contract_class: Option<Arc<RpcSierraContractClass>>,
7676

7777
// Invoke & Declare only field
7878
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -512,7 +512,7 @@ pub struct BroadcastedDeclareTx {
512512
/// a transaction to be valid for execution, the nonce must be equal to the account's current
513513
/// nonce.
514514
pub nonce: Nonce,
515-
pub contract_class: Arc<SierraClass>,
515+
pub contract_class: Arc<RpcSierraContractClass>,
516516
/// Data needed to allow the paymaster to pay for the transaction in native tokens.
517517
pub paymaster_data: Vec<Felt>,
518518
/// The tip for the transaction.

0 commit comments

Comments
 (0)