diff --git a/.cargo/config.toml b/.cargo/config.toml
index d277b56a032..440c785d471 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,7 +1,7 @@
[alias]
stacks-node = "run --package stacks-node --"
fmt-stacks = "fmt -- --config group_imports=StdExternalCrate,imports_granularity=Module"
-clippy-stacks = "clippy -p stx-genesis -p libstackerdb -p stacks-signer -p pox-locking -p clarity-types -p clarity -p libsigner -p stacks-common --no-deps --tests --all-features -- -D warnings"
+clippy-stacks = "clippy -p stx-genesis -p libstackerdb -p stacks-signer -p pox-locking -p clarity-types -p clarity -p libsigner -p stacks-common -p clarity-cli -p stacks-cli -p stacks-inspect --no-deps --tests --all-features -- -D warnings"
clippy-stackslib = "clippy -p stackslib --no-deps -- -Aclippy::all -Wclippy::indexing_slicing -Wclippy::nonminimal_bool -Wclippy::clone_on_copy"
# Uncomment to improve performance slightly, at the cost of portability
diff --git a/Cargo.lock b/Cargo.lock
index 31652204374..4e57b01a2ac 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -624,6 +624,22 @@ dependencies = [
"time 0.2.27",
]
+[[package]]
+name = "clarity-cli"
+version = "0.1.0"
+dependencies = [
+ "clarity 0.0.1",
+ "lazy_static",
+ "rand 0.8.5",
+ "rusqlite",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "slog",
+ "stacks-common 0.0.1",
+ "stackslib 0.0.1",
+]
+
[[package]]
name = "clarity-types"
version = "0.0.1"
@@ -3111,6 +3127,7 @@ name = "stacks-cli"
version = "0.1.0"
dependencies = [
"clarity 0.0.1",
+ "clarity-cli",
"serde_json",
"stacks-common 0.0.1",
"stackslib 0.0.1",
@@ -3179,6 +3196,7 @@ name = "stacks-inspect"
version = "0.1.0"
dependencies = [
"clarity 0.0.1",
+ "clarity-cli",
"libstackerdb 0.0.1",
"mutants",
"regex",
diff --git a/Cargo.toml b/Cargo.toml
index edbaa79db61..6e9c7507883 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ members = [
"stacks-signer",
"stacks-node",
"contrib/stacks-inspect",
+ "contrib/clarity-cli",
"contrib/stacks-cli"
]
diff --git a/contrib/clarity-cli/Cargo.toml b/contrib/clarity-cli/Cargo.toml
new file mode 100644
index 00000000000..1075c774d00
--- /dev/null
+++ b/contrib/clarity-cli/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "clarity-cli"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+clarity = { path = "../../clarity", default-features = false }
+stackslib = { package = "stackslib", path = "../../stackslib", default-features = false }
+stacks-common = { path = "../../stacks-common", default-features = false }
+slog = { version = "2.5.2", features = [ "max_level_trace" ] }
+lazy_static = { version = "1.4.0", default-features = false }
+serde = { version = "1" }
+serde_derive = "1"
+serde_json = { workspace = true }
+rand = { workspace = true }
+rusqlite = { workspace = true }
+
+[dev-dependencies]
+stacks-common = { path = "../../stacks-common", default-features = false, features = ["testing"] }
diff --git a/contrib/clarity-cli/README.md b/contrib/clarity-cli/README.md
new file mode 100644
index 00000000000..32ed0a6690a
--- /dev/null
+++ b/contrib/clarity-cli/README.md
@@ -0,0 +1,15 @@
+# clarity-cli
+
+A thin wrapper executable for the Clarity CLI exposed by `blockstack_lib::clarity_cli`. It forwards argv to the library, prints JSON output, and exits with the underlying status code.
+
+Build:
+```bash
+cargo build -p clarity-cli
+```
+
+Usage:
+```bash
+./target/debug/clarity-cli --help
+```
+
+For advanced usage and subcommands, see the upstream Clarity CLI documentation or run with `--help`.
diff --git a/stackslib/src/clarity_cli.rs b/contrib/clarity-cli/src/lib.rs
similarity index 90%
rename from stackslib/src/clarity_cli.rs
rename to contrib/clarity-cli/src/lib.rs
index 53dfd481cc9..c04b4c92e31 100644
--- a/stackslib/src/clarity_cli.rs
+++ b/contrib/clarity-cli/src/lib.rs
@@ -14,6 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+#[macro_use]
+extern crate serde_derive;
+
use std::ffi::OsStr;
use std::io::{Read, Write};
use std::path::PathBuf;
@@ -27,42 +30,42 @@ use serde::Serialize;
use serde_json::json;
use stacks_common::address::c32::c32_address;
use stacks_common::consts::{CHAIN_ID_MAINNET, CHAIN_ID_TESTNET};
+use stacks_common::debug;
use stacks_common::types::chainstate::{
BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, StacksAddress, StacksBlockId, VRFSeed,
};
use stacks_common::types::sqlite::NO_PARAMS;
use stacks_common::util::get_epoch_time_ms;
-use stacks_common::util::hash::{bytes_to_hex, Hash160, Sha512Trunc256Sum};
-
-use crate::burnchains::{PoxConstants, Txid};
-use crate::chainstate::stacks::boot::{
- BOOT_CODE_BNS, BOOT_CODE_COSTS, BOOT_CODE_COSTS_2, BOOT_CODE_COSTS_2_TESTNET,
- BOOT_CODE_COSTS_3, BOOT_CODE_COST_VOTING_MAINNET, BOOT_CODE_COST_VOTING_TESTNET,
- BOOT_CODE_GENESIS, BOOT_CODE_LOCKUP, BOOT_CODE_POX_MAINNET, BOOT_CODE_POX_TESTNET,
- POX_2_MAINNET_CODE, POX_2_TESTNET_CODE,
+use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum, bytes_to_hex};
+use stackslib::burnchains::{PoxConstants, Txid};
+use stackslib::chainstate::stacks::boot::{
+ BOOT_CODE_BNS, BOOT_CODE_COST_VOTING_MAINNET, BOOT_CODE_COST_VOTING_TESTNET, BOOT_CODE_COSTS,
+ BOOT_CODE_COSTS_2, BOOT_CODE_COSTS_2_TESTNET, BOOT_CODE_COSTS_3, BOOT_CODE_GENESIS,
+ BOOT_CODE_LOCKUP, BOOT_CODE_POX_MAINNET, BOOT_CODE_POX_TESTNET, POX_2_MAINNET_CODE,
+ POX_2_TESTNET_CODE,
};
-use crate::chainstate::stacks::index::ClarityMarfTrieId;
-use crate::clarity::vm::analysis::contract_interface_builder::build_contract_interface;
-use crate::clarity::vm::analysis::errors::CheckError;
-use crate::clarity::vm::analysis::{AnalysisDatabase, ContractAnalysis};
-use crate::clarity::vm::ast::build_ast;
-use crate::clarity::vm::contexts::{AssetMap, GlobalContext, OwnedEnvironment};
-use crate::clarity::vm::costs::{ExecutionCost, LimitedCostTracker};
-use crate::clarity::vm::database::{
- BurnStateDB, ClarityDatabase, HeadersDB, STXBalance, NULL_BURN_STATE_DB,
+use stackslib::chainstate::stacks::index::ClarityMarfTrieId;
+use stackslib::clarity::vm::analysis::contract_interface_builder::build_contract_interface;
+use stackslib::clarity::vm::analysis::errors::CheckError;
+use stackslib::clarity::vm::analysis::{AnalysisDatabase, ContractAnalysis};
+use stackslib::clarity::vm::ast::build_ast;
+use stackslib::clarity::vm::contexts::{AssetMap, GlobalContext, OwnedEnvironment};
+use stackslib::clarity::vm::costs::{ExecutionCost, LimitedCostTracker};
+use stackslib::clarity::vm::database::{
+ BurnStateDB, ClarityDatabase, HeadersDB, NULL_BURN_STATE_DB, STXBalance,
};
-use crate::clarity::vm::errors::{Error, InterpreterResult, RuntimeErrorType};
-use crate::clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
-use crate::clarity::vm::{
- analysis, ast, eval_all, ClarityVersion, ContractContext, ContractName, SymbolicExpression,
- Value,
+use stackslib::clarity::vm::errors::{Error, InterpreterResult, RuntimeErrorType};
+use stackslib::clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
+use stackslib::clarity::vm::{
+ ClarityVersion, ContractContext, ContractName, SymbolicExpression, Value, analysis, ast,
+ eval_all,
};
-use crate::clarity_vm::clarity::{ClarityMarfStore, ClarityMarfStoreTransaction};
-use crate::clarity_vm::database::marf::{MarfedKV, PersistentWritableMarfStore};
-use crate::clarity_vm::database::MemoryBackingStore;
-use crate::core::{StacksEpochId, BLOCK_LIMIT_MAINNET_205, HELIUM_BLOCK_LIMIT_20};
-use crate::util_lib::boot::{boot_code_addr, boot_code_id};
-use crate::util_lib::db::{sqlite_open, FromColumn};
+use stackslib::clarity_vm::clarity::{ClarityMarfStore, ClarityMarfStoreTransaction};
+use stackslib::clarity_vm::database::MemoryBackingStore;
+use stackslib::clarity_vm::database::marf::{MarfedKV, PersistentWritableMarfStore};
+use stackslib::core::{BLOCK_LIMIT_MAINNET_205, HELIUM_BLOCK_LIMIT_20, StacksEpochId};
+use stackslib::util_lib::boot::{boot_code_addr, boot_code_id};
+use stackslib::util_lib::db::{FromColumn, sqlite_open};
lazy_static! {
pub static ref STACKS_BOOT_CODE_MAINNET_2_1: [(&'static str, &'static str); 9] = [
@@ -104,7 +107,7 @@ macro_rules! panic_test {
fn print_usage(invoked_by: &str) {
eprintln!(
- "Usage: {} [command]
+ "Usage: {invoked_by} [command]
where command is one of:
initialize to initialize a local VM state database.
@@ -118,22 +121,21 @@ where command is one of:
repl to typecheck and evaluate expressions in a stdin/stdout loop.
execute to execute a public function of a defined contract.
generate_address to generate a random Stacks public address for testing purposes.
-",
- invoked_by
+"
);
panic_test!()
}
fn friendly_expect(input: Result, msg: &str) -> A {
input.unwrap_or_else(|e| {
- eprintln!("{}\nCaused by: {}", msg, e);
+ eprintln!("{msg}\nCaused by: {e}");
panic_test!();
})
}
fn friendly_expect_opt(input: Option, msg: &str) -> A {
input.unwrap_or_else(|| {
- eprintln!("{}", msg);
+ eprintln!("{msg}");
panic_test!();
})
}
@@ -141,6 +143,7 @@ fn friendly_expect_opt(input: Option, msg: &str) -> A {
pub const DEFAULT_CLI_EPOCH: StacksEpochId = StacksEpochId::Epoch32;
struct EvalInput {
+ #[allow(dead_code)]
marf_kv: MarfedKV,
contract_identifier: QualifiedContractIdentifier,
content: String,
@@ -269,7 +272,7 @@ fn create_or_open_db(path: &String) -> Connection {
}
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE
} else {
- panic!("FATAL: could not stat {}", path);
+ panic!("FATAL: could not stat {path}");
}
}
Ok(_md) => {
@@ -279,11 +282,10 @@ fn create_or_open_db(path: &String) -> Connection {
}
};
- let conn = friendly_expect(
+ friendly_expect(
sqlite_open(path, open_flags, false),
- &format!("FATAL: failed to open '{}'", path),
- );
- conn
+ &format!("FATAL: failed to open '{path}'"),
+ )
}
fn get_cli_chain_tip(conn: &Connection) -> StacksBlockId {
@@ -311,16 +313,11 @@ fn get_cli_block_height(conn: &Connection, block_id: &StacksBlockId) -> Option String {
@@ -330,11 +327,11 @@ fn get_cli_db_path(db_path: &str) -> String {
let mut cli_db_path_buf = PathBuf::from(db_path);
cli_db_path_buf.push("cli.sqlite");
- let cli_db_path = cli_db_path_buf
+
+ cli_db_path_buf
.to_str()
- .unwrap_or_else(|| panic!("FATAL: failed to convert '{}' to a string", db_path))
- .to_string();
- cli_db_path
+ .unwrap_or_else(|| panic!("FATAL: failed to convert '{db_path}' to a string"))
+ .to_string()
}
// This function is pretty weird! But it helps cut down on
@@ -397,12 +394,11 @@ where
}
fn default_chain_id(mainnet: bool) -> u32 {
- let chain_id = if mainnet {
+ if mainnet {
CHAIN_ID_MAINNET
} else {
CHAIN_ID_TESTNET
- };
- chain_id
+ }
}
fn with_env_costs(
@@ -478,7 +474,7 @@ fn save_coverage(
match (coverage_folder, coverage) {
(Some(coverage_folder), Some(coverage)) => {
let mut coverage_file = PathBuf::from(coverage_folder);
- coverage_file.push(&format!("{}_{}", prefix, get_epoch_time_ms()));
+ coverage_file.push(format!("{prefix}_{}", get_epoch_time_ms()));
coverage_file.set_extension("clarcov");
coverage
@@ -501,7 +497,7 @@ impl CLIHeadersDB {
let cli_db_path = self.get_cli_db_path();
let tx = friendly_expect(
self.conn.transaction(),
- &format!("FATAL: failed to begin transaction on '{}'", cli_db_path),
+ &format!("FATAL: failed to begin transaction on '{cli_db_path}'"),
);
friendly_expect(
@@ -522,7 +518,7 @@ impl CLIHeadersDB {
if !mainnet {
friendly_expect(
- tx.execute("INSERT INTO cli_config (testnet) VALUES (?1)", &[&true]),
+ tx.execute("INSERT INTO cli_config (testnet) VALUES (?1)", [&true]),
"FATAL: failed to set testnet flag",
);
}
@@ -535,7 +531,7 @@ impl CLIHeadersDB {
/// Create or open a new CLI DB at db_path. If it already exists, then this method is a no-op.
pub fn new(db_path: &str, mainnet: bool) -> CLIHeadersDB {
- let instantiate = db_path == ":memory:" || fs::metadata(&db_path).is_err();
+ let instantiate = db_path == ":memory:" || fs::metadata(db_path).is_err();
let cli_db_path = get_cli_db_path(db_path);
let conn = create_or_open_db(&cli_db_path);
@@ -569,8 +565,7 @@ impl CLIHeadersDB {
/// Make a new CLI DB in memory.
pub fn new_memory(mainnet: bool) -> CLIHeadersDB {
- let db = CLIHeadersDB::new(":memory:", mainnet);
- db
+ CLIHeadersDB::new(":memory:", mainnet)
}
fn get_cli_db_path(&self) -> String {
@@ -603,7 +598,7 @@ impl CLIHeadersDB {
let parent_block_hash = get_cli_chain_tip(&tx);
- let random_bytes = rand::thread_rng().gen::<[u8; 32]>();
+ let random_bytes = rand::thread_rng().r#gen::<[u8; 32]>();
let next_block_hash = friendly_expect_opt(
StacksBlockId::from_bytes(&random_bytes),
"Failed to generate random block header.",
@@ -612,7 +607,7 @@ impl CLIHeadersDB {
friendly_expect(
tx.execute(
"INSERT INTO cli_chain_tips (block_hash) VALUES (?1)",
- &[&next_block_hash],
+ [&next_block_hash],
),
&format!(
"FATAL: failed to store next block hash in '{}'",
@@ -701,29 +696,17 @@ impl HeadersDB for CLIHeadersDB {
_epoch: Option<&StacksEpochId>,
) -> Option {
let conn = self.conn();
- if let Some(height) = get_cli_block_height(conn, id_bhh) {
- Some(height * 600 + 1231006505)
- } else {
- None
- }
+ get_cli_block_height(conn, id_bhh).map(|height| height * 600 + 1231006505)
}
fn get_stacks_block_time_for_block(&self, id_bhh: &StacksBlockId) -> Option {
let conn = self.conn();
- if let Some(height) = get_cli_block_height(conn, id_bhh) {
- Some(height * 10 + 1713799973)
- } else {
- None
- }
+ get_cli_block_height(conn, id_bhh).map(|height| height * 10 + 1713799973)
}
fn get_burn_block_height_for_block(&self, id_bhh: &StacksBlockId) -> Option {
let conn = self.conn();
- if let Some(height) = get_cli_block_height(conn, id_bhh) {
- Some(height as u32)
- } else {
- None
- }
+ get_cli_block_height(conn, id_bhh).map(|height| height as u32)
}
fn get_miner_address(
@@ -773,8 +756,8 @@ impl HeadersDB for CLIHeadersDB {
fn get_eval_input(invoked_by: &str, args: &[String]) -> EvalInput {
if args.len() < 3 || args.len() > 4 {
eprintln!(
- "Usage: {} {} [--costs] [contract-identifier] (program.clar) [vm-state.db]",
- invoked_by, args[0]
+ "Usage: {invoked_by} {} [--costs] [contract-identifier] (program.clar) [vm-state.db]",
+ args[0]
);
panic_test!();
}
@@ -807,11 +790,11 @@ fn get_eval_input(invoked_by: &str, args: &[String]) -> EvalInput {
"Failed to open VM database.",
);
// return (marf_kv, contract_identifier, vm_filename, content);
- return EvalInput {
+ EvalInput {
marf_kv,
contract_identifier,
content,
- };
+ }
}
#[derive(Serialize, Deserialize)]
@@ -827,7 +810,7 @@ fn consume_arg(
) -> Result