Skip to content

Commit b2ca15c

Browse files
committed
feat: post compilation size limit validation
1 parent fb92713 commit b2ca15c

6 files changed

+142
-10
lines changed

crates/gateway/src/compilation.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,22 @@ use crate::utils::is_subsequence;
1818
#[path = "compilation_test.rs"]
1919
mod compilation_test;
2020

21+
// TODO(Arni): Move the gateway compilation util to a dedicated file.
22+
// TODO(Arni): Find a better place for this config.
23+
pub struct SierraToCasmCompilationConfig {
24+
pub max_bytecode_size: usize,
25+
pub max_raw_class_size: usize,
26+
}
27+
28+
// TODO(Define a function for `compile_contract_class` - which ignores the `config` parameter).
29+
2130
/// Formats the contract class for compilation, compiles it, and returns the compiled contract class
2231
/// wrapped in a [`ClassInfo`].
2332
/// Assumes the contract class is of a Sierra program which is compiled to Casm.
24-
pub fn compile_contract_class(declare_tx: &RPCDeclareTransaction) -> GatewayResult<ClassInfo> {
33+
pub fn compile_contract_class(
34+
declare_tx: &RPCDeclareTransaction,
35+
config: SierraToCasmCompilationConfig,
36+
) -> GatewayResult<ClassInfo> {
2537
let RPCDeclareTransaction::V3(tx) = declare_tx;
2638
let starknet_api_contract_class = &tx.contract_class;
2739
let cairo_lang_contract_class =
@@ -39,6 +51,40 @@ pub fn compile_contract_class(declare_tx: &RPCDeclareTransaction) -> GatewayResu
3951
};
4052
validate_casm_class(&casm_contract_class)?;
4153

54+
let bytecode_size = casm_contract_class.bytecode.len();
55+
if bytecode_size > config.max_bytecode_size {
56+
return Err(GatewayError::CasmBytecodeSizeTooLarge {
57+
bytecode_size,
58+
max_bytecode_size: config.max_bytecode_size,
59+
});
60+
}
61+
let contract_class_object_size = serde_json::to_string(&casm_contract_class)
62+
.expect("Unexpected error serializing Casm contract class.")
63+
.len();
64+
if contract_class_object_size > config.max_raw_class_size {
65+
return Err(GatewayError::CasmContractClassObjectSizeTooLarge {
66+
contract_class_object_size,
67+
max_contract_class_object_size: config.max_raw_class_size,
68+
});
69+
}
70+
71+
let bytecode_size = casm_contract_class.bytecode.len();
72+
if bytecode_size > config.max_bytecode_size {
73+
return Err(GatewayError::CasmBytecodeSizeTooLarge {
74+
bytecode_size,
75+
max_bytecode_size: config.max_bytecode_size,
76+
});
77+
}
78+
let contract_class_object_size = serde_json::to_string(&casm_contract_class)
79+
.expect("Unexpected error serializing Casm contract class.")
80+
.len();
81+
if contract_class_object_size > config.max_raw_class_size {
82+
return Err(GatewayError::CasmContractClassObjectSizeTooLarge {
83+
contract_class_object_size,
84+
max_contract_class_object_size: config.max_raw_class_size,
85+
});
86+
}
87+
4288
let hash_result = CompiledClassHash(casm_contract_class.compiled_class_hash());
4389
if hash_result != tx.compiled_class_hash {
4490
return Err(GatewayError::CompiledClassHashMismatch {

crates/gateway/src/compilation_test.rs

+52-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
use assert_matches::assert_matches;
22
use blockifier::execution::contract_class::ContractClass;
33
use cairo_lang_starknet_classes::allowed_libfuncs::AllowedLibfuncsError;
4+
use rstest::rstest;
45
use starknet_api::core::CompiledClassHash;
56
use starknet_api::rpc_transaction::{RPCDeclareTransaction, RPCTransaction};
67
use starknet_sierra_compile::errors::CompilationUtilError;
78
use test_utils::starknet_api_test_utils::declare_tx;
89

9-
use crate::compilation::compile_contract_class;
10+
use crate::compilation::{compile_contract_class, SierraToCasmCompilationConfig};
1011
use crate::errors::GatewayError;
1112

13+
const SIERRA_TO_CASM_COMPILATION_CONFIG: SierraToCasmCompilationConfig =
14+
SierraToCasmCompilationConfig { max_bytecode_size: usize::MAX, max_raw_class_size: usize::MAX };
15+
1216
#[test]
1317
fn test_compile_contract_class_compiled_class_hash_missmatch() {
1418
let mut tx = assert_matches!(
@@ -21,14 +25,57 @@ fn test_compile_contract_class_compiled_class_hash_missmatch() {
2125
tx.compiled_class_hash = supplied_hash;
2226
let declare_tx = RPCDeclareTransaction::V3(tx);
2327

24-
let result = compile_contract_class(&declare_tx);
28+
let result = compile_contract_class(&declare_tx, SIERRA_TO_CASM_COMPILATION_CONFIG);
2529
assert_matches!(
2630
result.unwrap_err(),
2731
GatewayError::CompiledClassHashMismatch { supplied, hash_result }
2832
if supplied == supplied_hash && hash_result == expected_hash_result
2933
);
3034
}
3135

36+
#[rstest]
37+
#[case::bytecode_size(
38+
SierraToCasmCompilationConfig { max_bytecode_size: 1, max_raw_class_size: usize::MAX},
39+
GatewayError::CasmBytecodeSizeTooLarge { bytecode_size: 4800, max_bytecode_size: 1 }
40+
)]
41+
#[case::raw_class_size(
42+
SierraToCasmCompilationConfig { max_bytecode_size: usize::MAX, max_raw_class_size: 1},
43+
GatewayError::CasmContractClassObjectSizeTooLarge {
44+
contract_class_object_size: 111037, max_contract_class_object_size: 1
45+
}
46+
)]
47+
fn test_compile_contract_class_size_validation(
48+
#[case] sierra_to_casm_compilation_config: SierraToCasmCompilationConfig,
49+
#[case] expected_error: GatewayError,
50+
) {
51+
let declare_tx = match declare_tx() {
52+
RPCTransaction::Declare(declare_tx) => declare_tx,
53+
_ => panic!("Invalid transaction type"),
54+
};
55+
56+
let result = compile_contract_class(&declare_tx, sierra_to_casm_compilation_config);
57+
if let GatewayError::CasmBytecodeSizeTooLarge {
58+
bytecode_size: expected_bytecode_size, ..
59+
} = expected_error
60+
{
61+
assert_matches!(
62+
result.unwrap_err(),
63+
GatewayError::CasmBytecodeSizeTooLarge { bytecode_size, .. }
64+
if bytecode_size == expected_bytecode_size
65+
)
66+
} else if let GatewayError::CasmContractClassObjectSizeTooLarge {
67+
contract_class_object_size: expected_contract_class_object_size,
68+
..
69+
} = expected_error
70+
{
71+
assert_matches!(
72+
result.unwrap_err(),
73+
GatewayError::CasmContractClassObjectSizeTooLarge { contract_class_object_size, .. }
74+
if contract_class_object_size == expected_contract_class_object_size
75+
)
76+
}
77+
}
78+
3279
#[test]
3380
fn test_compile_contract_class_bad_sierra() {
3481
let mut tx = assert_matches!(
@@ -39,7 +86,7 @@ fn test_compile_contract_class_bad_sierra() {
3986
tx.contract_class.sierra_program = tx.contract_class.sierra_program[..100].to_vec();
4087
let declare_tx = RPCDeclareTransaction::V3(tx);
4188

42-
let result = compile_contract_class(&declare_tx);
89+
let result = compile_contract_class(&declare_tx, SIERRA_TO_CASM_COMPILATION_CONFIG);
4390
assert_matches!(
4491
result.unwrap_err(),
4592
GatewayError::CompilationError(CompilationUtilError::AllowedLibfuncsError(
@@ -57,7 +104,8 @@ fn test_compile_contract_class() {
57104
let RPCDeclareTransaction::V3(declare_tx_v3) = &declare_tx;
58105
let contract_class = &declare_tx_v3.contract_class;
59106

60-
let class_info = compile_contract_class(&declare_tx).unwrap();
107+
let class_info =
108+
compile_contract_class(&declare_tx, SIERRA_TO_CASM_COMPILATION_CONFIG).unwrap();
61109
assert_matches!(class_info.contract_class(), ContractClass::V1(_));
62110
assert_eq!(class_info.sierra_program_length(), contract_class.sierra_program.len());
63111
assert_eq!(class_info.abi_length(), contract_class.abi.len());

crates/gateway/src/errors.rs

+13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ use crate::compiler_version::{VersionId, VersionIdError};
1919
/// Errors directed towards the end-user, as a result of gateway requests.
2020
#[derive(Debug, Error)]
2121
pub enum GatewayError {
22+
#[error(
23+
"Cannot declare Casm contract class with bytecode size of {bytecode_size}; max allowed \
24+
size: {max_bytecode_size}."
25+
)]
26+
CasmBytecodeSizeTooLarge { bytecode_size: usize, max_bytecode_size: usize },
27+
#[error(
28+
"Cannot declare Casm contract class with size of {contract_class_object_size}; max \
29+
allowed size: {max_contract_class_object_size}."
30+
)]
31+
CasmContractClassObjectSizeTooLarge {
32+
contract_class_object_size: usize,
33+
max_contract_class_object_size: usize,
34+
},
2235
#[error(transparent)]
2336
CompilationError(#[from] CompilationUtilError),
2437
#[error(

crates/gateway/src/gateway.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use starknet_mempool_infra::component_runner::{ComponentRunner, ComponentStartEr
1212
use starknet_mempool_types::communication::SharedMempoolClient;
1313
use starknet_mempool_types::mempool_types::{Account, MempoolInput};
1414

15-
use crate::compilation::compile_contract_class;
15+
use crate::compilation::{compile_contract_class, SierraToCasmCompilationConfig};
1616
use crate::config::{GatewayConfig, GatewayNetworkConfig, RpcStateReaderConfig};
1717
use crate::errors::{GatewayError, GatewayResult, GatewayRunError};
1818
use crate::rpc_state_reader::RpcStateReaderFactory;
@@ -118,8 +118,14 @@ fn process_tx(
118118
stateless_tx_validator.validate(&tx)?;
119119

120120
// Compile Sierra to Casm.
121+
let sierra_to_casm_compilation_config = SierraToCasmCompilationConfig {
122+
max_bytecode_size: stateless_tx_validator.config.max_bytecode_size,
123+
max_raw_class_size: stateless_tx_validator.config.max_raw_class_size,
124+
};
121125
let optional_class_info = match &tx {
122-
RPCTransaction::Declare(declare_tx) => Some(compile_contract_class(declare_tx)?),
126+
RPCTransaction::Declare(declare_tx) => {
127+
Some(compile_contract_class(declare_tx, sierra_to_casm_compilation_config)?)
128+
}
123129
_ => None,
124130
};
125131

crates/gateway/src/gateway_test.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use test_utils::starknet_api_test_utils::{declare_tx, deploy_account_tx, invoke_
1717
use tokio::sync::mpsc::channel;
1818
use tokio::task;
1919

20+
use crate::compilation::SierraToCasmCompilationConfig;
2021
use crate::config::{StatefulTransactionValidatorConfig, StatelessTransactionValidatorConfig};
2122
use crate::gateway::{add_tx, compile_contract_class, AppState, SharedMempoolClient};
2223
use crate::state_reader_test_utils::{
@@ -110,7 +111,16 @@ async fn to_bytes(res: Response) -> Bytes {
110111

111112
fn calculate_hash(external_tx: &RPCTransaction) -> TransactionHash {
112113
let optional_class_info = match &external_tx {
113-
RPCTransaction::Declare(declare_tx) => Some(compile_contract_class(declare_tx).unwrap()),
114+
RPCTransaction::Declare(declare_tx) => Some(
115+
compile_contract_class(
116+
declare_tx,
117+
SierraToCasmCompilationConfig {
118+
max_bytecode_size: usize::MAX,
119+
max_raw_class_size: usize::MAX,
120+
},
121+
)
122+
.unwrap(),
123+
),
114124
_ => None,
115125
};
116126

crates/gateway/src/stateful_transaction_validator_test.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use test_utils::starknet_api_test_utils::{
1212
VALID_L1_GAS_MAX_PRICE_PER_UNIT,
1313
};
1414

15-
use crate::compilation::compile_contract_class;
15+
use crate::compilation::{compile_contract_class, SierraToCasmCompilationConfig};
1616
use crate::config::StatefulTransactionValidatorConfig;
1717
use crate::errors::{StatefulTransactionValidatorError, StatefulTransactionValidatorResult};
1818
use crate::state_reader_test_utils::{
@@ -81,7 +81,16 @@ fn test_stateful_tx_validator(
8181
},
8282
};
8383
let optional_class_info = match &external_tx {
84-
RPCTransaction::Declare(declare_tx) => Some(compile_contract_class(declare_tx).unwrap()),
84+
RPCTransaction::Declare(declare_tx) => Some(
85+
compile_contract_class(
86+
declare_tx,
87+
SierraToCasmCompilationConfig {
88+
max_bytecode_size: usize::MAX,
89+
max_raw_class_size: usize::MAX,
90+
},
91+
)
92+
.unwrap(),
93+
),
8594
_ => None,
8695
};
8796

0 commit comments

Comments
 (0)