Skip to content

Commit

Permalink
feat: add declare specific stateless tx validations
Browse files Browse the repository at this point in the history
  • Loading branch information
ArniStarkware committed Jun 16, 2024
1 parent 2da71c2 commit 3d25ca3
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 3 deletions.
2 changes: 2 additions & 0 deletions crates/gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ pub struct StatelessTransactionValidatorConfig {
// Declare txs specific config.
pub max_bytecode_size: usize,
pub max_raw_class_size: usize,
// TODO(Arni): Add sierra version limitations. Validate the Sierra version is:
// cairo_lang_starknet_classes::compiler_version::current_sierra_version_id.
}

impl SerializeConfig for StatelessTransactionValidatorConfig {
Expand Down
9 changes: 9 additions & 0 deletions crates/gateway/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use axum::response::{IntoResponse, Response};
use blockifier::blockifier::stateful_validator::StatefulValidatorError;
use blockifier::state::errors::StateError;
use blockifier::transaction::errors::TransactionExecutionError;
use cairo_lang_starknet_classes::compiler_version::VersionId;
use starknet_api::block::BlockNumber;
use starknet_api::hash::StarkFelt;
use starknet_api::transaction::{Resource, ResourceBounds};
use starknet_api::StarknetApiError;
use thiserror::Error;
Expand Down Expand Up @@ -46,6 +48,13 @@ pub enum StatelessTransactionValidatorError {
(allowed length: {max_signature_length})."
)]
SignatureTooLong { signature_length: usize, max_signature_length: usize },
#[error("Invalid Sierra version: {version:?}.")]
InvalidSierraVersion { version: [StarkFelt; 3] },
#[error(
"Compiled versions older than {min_version} or newer than {max_version} are not \
supported. Got {version}."
)]
UnsupportedSierraVersion { version: VersionId, min_version: VersionId, max_version: VersionId },
#[error(
"Cannot declare contract class with bytecode size of {bytecode_size}; max allowed size: \
{max_bytecode_size}."
Expand Down
5 changes: 3 additions & 2 deletions crates/gateway/src/starknet_api_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ pub fn external_tx_for_testing(
) -> ExternalTransaction {
match tx_type {
TransactionType::Declare => {
// Minimal contract class.
let contract_class = ContractClass {
sierra_program: vec![stark_felt!(1_u32); 3],
..ContractClass::default()
sierra_program: vec![stark_felt!(1_u32), stark_felt!(3_u32), stark_felt!(0_u32)],
..Default::default()
};
external_declare_tx(declare_tx_args!(resource_bounds, signature, contract_class))
}
Expand Down
68 changes: 68 additions & 0 deletions crates/gateway/src/stateless_transaction_validator.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use cairo_lang_starknet_classes::compiler_version::VersionId;
use starknet_api::external_transaction::{
ExternalDeclareTransaction, ExternalDeployAccountTransaction, ExternalInvokeTransaction,
ExternalTransaction, ResourceBoundsMapping,
};
use starknet_api::hash::StarkFelt;
use starknet_api::transaction::Resource;

use crate::config::StatelessTransactionValidatorConfig;
use crate::errors::{StatelessTransactionValidatorError, StatelessTransactionValidatorResult};

// TODO(Arni): Get from config.
pub const MAX_SIERRA_VERSION: VersionId = VersionId { major: 1, minor: 5, patch: 0 };
pub const MIN_SIERRA_VERSION: VersionId = VersionId { major: 1, minor: 1, patch: 0 };

#[cfg(test)]
#[path = "stateless_transaction_validator_test.rs"]
mod stateless_transaction_validator_test;
Expand Down Expand Up @@ -106,9 +112,40 @@ impl StatelessTransactionValidator {
let contract_class = match declare_tx {
ExternalDeclareTransaction::V3(tx) => &tx.contract_class,
};
self.validate_sierra_version(&contract_class.sierra_program)?;
self.validate_class_length(contract_class)
}

fn validate_sierra_version(
&self,
sierra_program: &[StarkFelt],
) -> StatelessTransactionValidatorResult<()> {
let sierra_version = sierra_program_version_id(sierra_program)?;

// Check that the version is not too old.
if less_then(sierra_version, MIN_SIERRA_VERSION) {
return Err(StatelessTransactionValidatorError::UnsupportedSierraVersion {
version: sierra_version,
min_version: MIN_SIERRA_VERSION,
max_version: MAX_SIERRA_VERSION,
});
}
// Check that the version is lower than the latest version allowing higher patch versions
// (i.e. we ignore the Z part in a version X.Y.Z).
let max_minor_sierra_version = VersionId { patch: 0, ..MAX_SIERRA_VERSION };
let minor_sierra_version = VersionId { patch: 0, ..sierra_version };

if less_then(max_minor_sierra_version, minor_sierra_version) {
return Err(StatelessTransactionValidatorError::UnsupportedSierraVersion {
version: sierra_version,
min_version: MIN_SIERRA_VERSION,
max_version: MAX_SIERRA_VERSION,
});
}

Ok(())
}

fn validate_class_length(
&self,
contract_class: &starknet_api::external_transaction::ContractClass,
Expand All @@ -135,6 +172,37 @@ impl StatelessTransactionValidator {
}
}

fn sierra_program_version_id(
sierra_program: &[StarkFelt],
) -> StatelessTransactionValidatorResult<VersionId> {
let length_of_version = sierra_program.len().min(3);
let mut version = [StarkFelt::default(); 3];
version[..length_of_version].copy_from_slice(&sierra_program[..length_of_version]);

if length_of_version < 3 {
return Err(StatelessTransactionValidatorError::InvalidSierraVersion { version });
}

let map_err = || StatelessTransactionValidatorError::InvalidSierraVersion { version };
Ok(VersionId {
major: sierra_program[0].try_into().map_err(|_| map_err())?,
minor: sierra_program[1].try_into().map_err(|_| map_err())?,
patch: sierra_program[2].try_into().map_err(|_| map_err())?,
})
}

fn less_then(lhs: VersionId, rhs: VersionId) -> bool {
match lhs.major.cmp(&rhs.major) {
std::cmp::Ordering::Less => true,
std::cmp::Ordering::Greater => false,
std::cmp::Ordering::Equal => match lhs.minor.cmp(&rhs.minor) {
std::cmp::Ordering::Less => true,
std::cmp::Ordering::Greater => false,
std::cmp::Ordering::Equal => lhs.patch < rhs.patch,
},
}
}

fn validate_resource_is_non_zero(
resource_bounds_mapping: &ResourceBoundsMapping,
resource: Resource,
Expand Down
89 changes: 88 additions & 1 deletion crates/gateway/src/stateless_transaction_validator_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use assert_matches::assert_matches;
use cairo_lang_starknet_classes::compiler_version::VersionId;
use rstest::rstest;
use starknet_api::external_transaction::{ContractClass, ResourceBoundsMapping};
use starknet_api::hash::StarkFelt;
Expand All @@ -12,7 +13,8 @@ use crate::starknet_api_test_utils::{
zero_resource_bounds_mapping, TransactionType, NON_EMPTY_RESOURCE_BOUNDS,
};
use crate::stateless_transaction_validator::{
StatelessTransactionValidator, StatelessTransactionValidatorError,
StatelessTransactionValidator, StatelessTransactionValidatorError, MAX_SIERRA_VERSION,
MIN_SIERRA_VERSION,
};

const DEFAULT_VALIDATOR_CONFIG_FOR_TESTING: StatelessTransactionValidatorConfig =
Expand Down Expand Up @@ -184,6 +186,91 @@ fn test_signature_too_long(
);
}

#[rstest]
fn test_declare_sierra_program_too_short(
#[values(
vec![],
vec![stark_felt!(1_u128)],
vec![stark_felt!(1_u128), stark_felt!(3_u128)]
)]
sierra_program: Vec<StarkFelt>,
) {
let tx_validator =
StatelessTransactionValidator { config: DEFAULT_VALIDATOR_CONFIG_FOR_TESTING };

let contract_class = ContractClass { sierra_program, ..Default::default() };
let tx = external_declare_tx(declare_tx_args!(contract_class));

assert_matches!(
tx_validator.validate(&tx).unwrap_err(),
StatelessTransactionValidatorError::InvalidSierraVersion { .. }
);
}

#[rstest]
#[case::invalid_sierra_version(
vec![
stark_felt!(1_u128),
stark_felt!(3_u128),
stark_felt!(0x10000000000000000_u128), // Does not fit into a usize.
],
StatelessTransactionValidatorError::InvalidSierraVersion {
version: [
stark_felt!(1_u128),
stark_felt!(3_u128),
stark_felt!(0x10000000000000000_u128)
]
}
)
]
#[case::sierra_version_too_low(
vec![stark_felt!(0_u128), stark_felt!(3_u128), stark_felt!(0_u128)],
StatelessTransactionValidatorError::UnsupportedSierraVersion {
version: VersionId{major: 0, minor: 3, patch: 0},
min_version: MIN_SIERRA_VERSION,
max_version: MAX_SIERRA_VERSION,
})
]
#[case::sierra_version_too_high(
vec![stark_felt!(1_u128), stark_felt!(6_u128), stark_felt!(0_u128)],
StatelessTransactionValidatorError::UnsupportedSierraVersion {
version: VersionId { major: 1, minor: 6, patch: 0 },
min_version: MIN_SIERRA_VERSION,
max_version: MAX_SIERRA_VERSION
})
]
fn test_declare_sierra_version(
#[case] sierra_program: Vec<StarkFelt>,
#[case] expected_error: StatelessTransactionValidatorError,
) {
let tx_validator =
StatelessTransactionValidator { config: DEFAULT_VALIDATOR_CONFIG_FOR_TESTING };

let contract_class = ContractClass { sierra_program, ..Default::default() };
let tx = external_declare_tx(declare_tx_args!(contract_class));

assert_eq!(tx_validator.validate(&tx).unwrap_err(), expected_error);
}

#[rstest]
#[case::min_sierra_version(MIN_SIERRA_VERSION)]
#[case::valid_sierra_version(VersionId { major: 1, minor: 3, patch: 0 })]
#[case::max_sierra_version(MAX_SIERRA_VERSION)]
fn positive_flow_test_declare_sierra_version(#[case] sierra_version: VersionId) {
let tx_validator =
StatelessTransactionValidator { config: DEFAULT_VALIDATOR_CONFIG_FOR_TESTING };

let sierra_program = vec![
stark_felt!(u64::try_from(sierra_version.major).unwrap()),
stark_felt!(u64::try_from(sierra_version.minor).unwrap()),
stark_felt!(u64::try_from(sierra_version.patch).unwrap()),
];
let contract_class = ContractClass { sierra_program, ..Default::default() };
let tx = external_declare_tx(declare_tx_args!(contract_class));

assert_matches!(tx_validator.validate(&tx), Ok(()));
}

#[test]
fn test_declare_bytecode_size_too_long() {
let config_max_bytecode_size = 10;
Expand Down

0 comments on commit 3d25ca3

Please sign in to comment.