Skip to content

refactoring: Isolate clarity VM behind feature gate for lightweight serialization #6239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
82fbfb1
make http-parser and ctrlc optional features
Jiloc Jun 30, 2025
cb16f1a
remove regex from stacks-common
Jiloc Jun 30, 2025
f348b65
move serde_json to workspace. refactor stacks common cargo
Jiloc Jul 1, 2025
6775c85
removed time
Jiloc Jul 1, 2025
7442358
add vm feature flag
Jiloc Jul 1, 2025
75b0191
fix format
Jiloc Jul 2, 2025
5e2eeac
clippy
Jiloc Jul 2, 2025
d186115
make rand optional for CosmWasm support
Jiloc Jul 3, 2025
a9c14bf
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 3, 2025
97c968c
substitute ::generate with ::from_bytes
Jiloc Jul 3, 2025
d64ad0b
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 3, 2025
f6621cb
add custom dummy getdefault
Jiloc Jul 4, 2025
526c0d1
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 4, 2025
309e98d
remove getrandom::register_custom_getrandom for wasm-deterministic
Jiloc Jul 4, 2025
9f9d721
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 4, 2025
5405302
remove unused rand-chacha
Jiloc Jul 7, 2025
492b0e0
proxy stacks-common wasm features in clarity
Jiloc Jul 7, 2025
c007bc6
update Cargo.lock
Jiloc Jul 7, 2025
018885a
Merge branch 'develop' into chore/clarity-vm-feature-gate
Jiloc Jul 7, 2025
2ebf15e
remove http-parser feature
Jiloc Jul 9, 2025
5783141
Merge branch 'develop' into chore/stacks-common-optional-requirements
Jiloc Jul 9, 2025
13aa4b5
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 9, 2025
908b21c
remove unused default features
Jiloc Jul 9, 2025
1029432
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 9, 2025
7dba0eb
remove unused slog import
Jiloc Jul 9, 2025
5694877
update cargo.lock
Jiloc Jul 9, 2025
4a70884
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 9, 2025
66d9077
disable unused defaul-features for clarity
Jiloc Jul 9, 2025
06b010a
remove clippy::assertions_on_constants
Jiloc Jul 10, 2025
dc7be7b
Merge branch 'develop' into chore/stacks-common-optional-requirements
Jiloc Jul 11, 2025
016ace1
Merge branch 'chore/stacks-common-optional-requirements' into chore/c…
Jiloc Jul 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
445 changes: 195 additions & 250 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,29 @@ members = [

# Dependencies we want to keep the same between workspace members
[workspace.dependencies]
ed25519-dalek = { version = "2.1.1", features = ["serde", "rand_core"] }
ed25519-dalek = { version = "2.1.1", default-features = false }
hashbrown = { version = "0.15.2", features = ["serde"] }
lazy_static = "1.4.0"
rand_core = "0.6.4"
rand = "0.8"
rand_chacha = "0.3.1"
tikv-jemallocator = "0.5.4"
serde = "1"
serde_derive = "1"
serde_json = { version = "1.0", features = ["arbitrary_precision", "unbounded_depth"] }
slog = { version = "2.5.2", features = [ "max_level_trace" ] }
rusqlite = { version = "0.31.0", features = ["blob", "serde_json", "i128_blob", "bundled", "trace"] }
tikv-jemallocator = "0.5.4"
thiserror = "1.0.65"
toml = "0.5.6"

# Use a bit more than default optimization for
# dev builds to speed up test execution
# dev builds to speed up test execution
[profile.dev]
opt-level = 1

# Use release-level optimization for dependencies
# This slows down "first" builds on development environments,
# but won't impact subsequent builds.
# but won't impact subsequent builds.
[profile.dev.package."*"]
opt-level = 3

Expand Down
60 changes: 34 additions & 26 deletions clarity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,46 @@ name = "clarity"
path = "./src/libclarity.rs"

[dependencies]
rand = { workspace = true }
rand_chacha = { workspace = true }
serde = "1"
serde_derive = "1"
serde_stacker = "0.1"
regex = "1"
lazy_static = "1.4.0"
integer-sqrt = "0.1.3"
slog = { version = "2.5.2", features = [ "max_level_trace" ] }
stacks_common = { package = "stacks-common", path = "../stacks-common", default-features = false }
rstest = "0.17.0"
rstest_reuse = "0.5.0"
hashbrown = { workspace = true }
rusqlite = { workspace = true, optional = true }
lazy_static = { workspace = true }
regex = { version = "1", default-features = false }
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true }
slog = { workspace = true }
stacks_common = { package = "stacks-common", path = "../stacks-common", default-features = false }

[dependencies.serde_json]
version = "1.0"
features = ["arbitrary_precision", "unbounded_depth"]
# Optional dependencies
rand = { workspace = true, optional = true }
serde_stacker = { version = "0.1", default-features = false, optional = true }
integer-sqrt = { version = "0.1.3", default-features = false, optional = true }
rusqlite = { workspace = true, optional = true }
rstest = { version = "0.17.0", default-features = false, optional = true }
rstest_reuse = { version = "0.5.0", default-features = false, optional = true }

[dev-dependencies]
assert-json-diff = "1.0.0"
mutants = "0.0.3"
# a nightly rustc regression (35dbef235 2021-03-02) prevents criterion from compiling
# but it isn't necessary for tests: only benchmarks. therefore, commenting out for now.
# criterion = "0.3"

[features]
default = ["rusqlite"]
developer-mode = ["stacks_common/developer-mode"]
# The default feature set provides the full Clarity virtual machine with its SQLite-based
# database backend.
# To use `clarity` as a lightweight serialization/deserialization library,
# depend on it with `default-features = false`.
default = ["vm", "rusqlite"]
# Enables the complete Clarity Virtual Machine. This includes the parser, analyzer,
# cost-checking system, and execution engine. It transitively enables all necessary
# dependencies for running smart contracts. This feature is required for any on-chain
# contract execution or local contract testing.
vm = ["dep:rand", "dep:serde_stacker", "dep:integer-sqrt"]
developer-mode = ["vm", "stacks_common/developer-mode"]
devtools = ["vm"]
testing = ["vm", "dep:rstest", "dep:rstest_reuse", "rusqlite"]
rusqlite = ["vm", "stacks_common/rusqlite", "dep:rusqlite"]
slog_json = ["stacks_common/slog_json"]
rusqlite = ["stacks_common/rusqlite", "dep:rusqlite"]
testing = []
devtools = []
rollback_value_check = []
disable-costs = []
rollback_value_check = ["vm"]
disable-costs = ["vm"]

# Wasm-specific features for easier configuration
wasm-web = ["stacks_common/wasm-web"]
wasm-deterministic = ["stacks_common/wasm-deterministic"]
5 changes: 1 addition & 4 deletions clarity/src/libclarity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
#![allow(non_upper_case_globals)]
#![cfg_attr(test, allow(unused_variables, unused_assignments))]

#[allow(unused_imports)]
#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error)]
extern crate slog;

#[macro_use]
extern crate serde_derive;

Expand All @@ -49,6 +45,7 @@ pub use stacks_common::{
/// The Clarity virtual machine
pub mod vm;

#[cfg(feature = "vm")]
pub mod boot_util {

use stacks_common::types::chainstate::StacksAddress;
Expand Down
10 changes: 5 additions & 5 deletions clarity/src/vm/analysis/arithmetic_checker/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,13 @@ fn test_functions_clarity1() {
Err(FunctionNotPermitted(NativeFunctions::SetVar))),
("(define-private (foo (a principal)) (ft-get-balance tokaroos a))",
Err(FunctionNotPermitted(NativeFunctions::GetTokenBalance))),
("(define-private (foo (a principal))
("(define-private (foo (a principal))
(ft-transfer? stackaroo u50 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF))",
Err(FunctionNotPermitted(NativeFunctions::TransferToken))),
("(define-private (foo (a principal))
("(define-private (foo (a principal))
(ft-mint? stackaroo u100 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))",
Err(FunctionNotPermitted(NativeFunctions::MintToken))),
("(define-private (foo (a principal))
("(define-private (foo (a principal))
(nft-mint? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))",
Err(FunctionNotPermitted(NativeFunctions::MintAsset))),
("(nft-transfer? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF)",
Expand Down Expand Up @@ -293,7 +293,7 @@ fn test_functions_clarity1() {
Ok(())),
("(buff-to-uint-be 0x0001)",
Ok(())),
("(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
("(is-standard 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
Ok(())),
("(principal-destruct? 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
Ok(())),
Expand Down Expand Up @@ -358,7 +358,7 @@ fn test_functions_clarity2() {
Err(FunctionNotPermitted(NativeFunctions::IsStandard))),
("(principal-destruct? 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6)",
Err(FunctionNotPermitted(NativeFunctions::PrincipalDestruct))),
("(principal-construct? 0x22 0xfa6bf38ed557fe417333710d6033e9419391a320)",
("(principal-construct? 0x22 0xfa6bf38ed557fe417333710d6033e9419391a320)",
Err(FunctionNotPermitted(NativeFunctions::PrincipalConstruct))),
("(string-to-int? \"-1\")",
Err(FunctionNotPermitted(NativeFunctions::StringToInt))),
Expand Down
172 changes: 172 additions & 0 deletions clarity/src/vm/analysis/engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
// Copyright (C) 2020 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use stacks_common::types::StacksEpochId;

pub use crate::vm::analysis::analysis_db::AnalysisDatabase;
use crate::vm::analysis::arithmetic_checker::ArithmeticOnlyChecker;
use crate::vm::analysis::contract_interface_builder::build_contract_interface;
pub use crate::vm::analysis::errors::{CheckError, CheckErrors, CheckResult};
use crate::vm::analysis::read_only_checker::ReadOnlyChecker;
use crate::vm::analysis::trait_checker::TraitChecker;
use crate::vm::analysis::type_checker::v2_05::TypeChecker as TypeChecker2_05;
use crate::vm::analysis::type_checker::v2_1::TypeChecker as TypeChecker2_1;
pub use crate::vm::analysis::types::{AnalysisPass, ContractAnalysis};
#[cfg(feature = "rusqlite")]
use crate::vm::ast::{build_ast_with_rules, ASTRules};
use crate::vm::costs::LimitedCostTracker;
#[cfg(feature = "rusqlite")]
use crate::vm::database::MemoryBackingStore;
use crate::vm::database::STORE_CONTRACT_SRC_INTERFACE;
use crate::vm::representations::SymbolicExpression;
use crate::vm::types::QualifiedContractIdentifier;
#[cfg(feature = "rusqlite")]
use crate::vm::types::TypeSignature;
use crate::vm::ClarityVersion;

/// Used by CLI tools like the docs generator. Not used in production
#[cfg(feature = "rusqlite")]
pub fn mem_type_check(
snippet: &str,
version: ClarityVersion,
epoch: StacksEpochId,
) -> CheckResult<(Option<TypeSignature>, ContractAnalysis)> {
let contract_identifier: QualifiedContractIdentifier = QualifiedContractIdentifier::transient();
let contract: Vec<SymbolicExpression> = build_ast_with_rules(
&contract_identifier,
snippet,
&mut (),
version,
epoch,
ASTRules::PrecheckSize,
)
.map_err(|_| CheckErrors::Expects("Failed to build AST".into()))?
.expressions;

let mut marf: MemoryBackingStore = MemoryBackingStore::new();
let mut analysis_db = marf.as_analysis_db();
let cost_tracker = LimitedCostTracker::new_free();
match run_analysis(
&QualifiedContractIdentifier::transient(),
&contract,
&mut analysis_db,
false,
cost_tracker,
epoch,
version,
true,
) {
Ok(x) => {
// return the first type result of the type checker
let first_type = x
.type_map
.as_ref()
.ok_or_else(|| CheckErrors::Expects("Should be non-empty".into()))?
.get_type_expected(
x.expressions
.last()
.ok_or_else(|| CheckErrors::Expects("Should be non-empty".into()))?,
)
.cloned();
Ok((first_type, x))
}
Err((e, _)) => Err(e),
}
}

// Legacy function
// The analysis is not just checking type.
#[cfg(test)]
pub fn type_check(
contract_identifier: &QualifiedContractIdentifier,
expressions: &mut [SymbolicExpression],
analysis_db: &mut AnalysisDatabase,
insert_contract: bool,
epoch: &StacksEpochId,
version: &ClarityVersion,
) -> CheckResult<ContractAnalysis> {
run_analysis(
contract_identifier,
expressions,
analysis_db,
insert_contract,
// for the type check tests, the cost tracker's epoch doesn't
// matter: the costs in those tests are all free anyways.
LimitedCostTracker::new_free(),
*epoch,
*version,
true,
)
.map_err(|(e, _cost_tracker)| e)
}

#[allow(clippy::too_many_arguments)]
pub fn run_analysis(
contract_identifier: &QualifiedContractIdentifier,
expressions: &[SymbolicExpression],
analysis_db: &mut AnalysisDatabase,
save_contract: bool,
cost_tracker: LimitedCostTracker,
epoch: StacksEpochId,
version: ClarityVersion,
build_type_map: bool,
) -> Result<ContractAnalysis, (CheckError, LimitedCostTracker)> {
let mut contract_analysis = ContractAnalysis::new(
contract_identifier.clone(),
expressions.to_vec(),
cost_tracker,
epoch,
version,
);
let result = analysis_db.execute(|db| {
ReadOnlyChecker::run_pass(&epoch, &mut contract_analysis, db)?;
match epoch {
StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => {
TypeChecker2_05::run_pass(&epoch, &mut contract_analysis, db, build_type_map)
}
StacksEpochId::Epoch21
| StacksEpochId::Epoch22
| StacksEpochId::Epoch23
| StacksEpochId::Epoch24
| StacksEpochId::Epoch25
| StacksEpochId::Epoch30
| StacksEpochId::Epoch31 => {
TypeChecker2_1::run_pass(&epoch, &mut contract_analysis, db, build_type_map)
}
StacksEpochId::Epoch10 => {
return Err(CheckErrors::Expects(
"Epoch 1.0 is not a valid epoch for analysis".into(),
)
.into())
}
}?;
TraitChecker::run_pass(&epoch, &mut contract_analysis, db)?;
ArithmeticOnlyChecker::check_contract_cost_eligible(&mut contract_analysis);

if STORE_CONTRACT_SRC_INTERFACE {
let interface = build_contract_interface(&contract_analysis)?;
contract_analysis.contract_interface = Some(interface);
}
if save_contract {
db.insert_contract(contract_identifier, &contract_analysis)?;
}
Ok(())
});
match result {
Ok(_) => Ok(contract_analysis),
Err(e) => Err((e, contract_analysis.take_contract_cost_tracker())),
}
}
Loading
Loading