Skip to content

Commit 38aaeaa

Browse files
committed
test larger contract analysis with pox-4
1 parent b50265d commit 38aaeaa

File tree

2 files changed

+125
-21
lines changed

2 files changed

+125
-21
lines changed

clarity/src/vm/costs/analysis.rs

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -953,30 +953,81 @@ fn build_listlike_cost_analysis_tree(
953953
children.push(child_tree);
954954
}
955955

956-
let function_name = get_function_name(&exprs[0])?;
957-
// Try to lookup the function as a native function first
958-
let (expr_node, cost) = if let Some(native_function) =
959-
NativeFunctions::lookup_by_name_at_version(function_name.as_str(), clarity_version)
960-
{
961-
CostExprNode::NativeFunction(native_function);
962-
let cost = calculate_function_cost_from_native_function(
963-
native_function,
964-
children.len() as u64,
965-
clarity_version,
966-
)?;
967-
(CostExprNode::NativeFunction(native_function), cost)
968-
} else {
969-
// If not a native function, treat as user-defined function and look it up
970-
let expr_node = CostExprNode::UserFunction(function_name.clone());
971-
let cost = calculate_function_cost(function_name.to_string(), cost_map, clarity_version)?;
972-
(expr_node, cost)
956+
// Try to get function name from first element
957+
let (expr_node, cost, function_name_opt) = match get_function_name(&exprs[0]) {
958+
Ok(function_name) => {
959+
// Try to lookup the function as a native function first
960+
if let Some(native_function) =
961+
NativeFunctions::lookup_by_name_at_version(function_name.as_str(), clarity_version)
962+
{
963+
let cost = calculate_function_cost_from_native_function(
964+
native_function,
965+
children.len() as u64,
966+
clarity_version,
967+
)?;
968+
(
969+
CostExprNode::NativeFunction(native_function),
970+
cost,
971+
Some(function_name),
972+
)
973+
} else {
974+
// If not a native function, treat as user-defined function and look it up
975+
let expr_node = CostExprNode::UserFunction(function_name.clone());
976+
let cost =
977+
calculate_function_cost(function_name.to_string(), cost_map, clarity_version)?;
978+
(expr_node, cost, Some(function_name))
979+
}
980+
}
981+
Err(_) => {
982+
// First element is not an atom - it might be a List that needs to be recursively analyzed
983+
match &exprs[0].expr {
984+
SymbolicExpressionType::List(_) => {
985+
// Recursively analyze the nested list structure
986+
let (_, nested_tree) =
987+
build_cost_analysis_tree(&exprs[0], user_args, cost_map, clarity_version)?;
988+
// Add the nested tree as a child (its cost will be included when summing children)
989+
children.insert(0, nested_tree);
990+
// The root cost is zero - the actual cost comes from the nested expression
991+
let expr_node = CostExprNode::Atom(ClarityName::from("nested-expression"));
992+
(expr_node, StaticCost::ZERO, None)
993+
}
994+
SymbolicExpressionType::Atom(name) => {
995+
// It's an atom but not a function name - treat as atom with zero cost
996+
(CostExprNode::Atom(name.clone()), StaticCost::ZERO, None)
997+
}
998+
SymbolicExpressionType::AtomValue(value) => {
999+
// It's an atom value - calculate its cost
1000+
let cost = calculate_value_cost(value)?;
1001+
(CostExprNode::AtomValue(value.clone()), cost, None)
1002+
}
1003+
SymbolicExpressionType::TraitReference(trait_name, _trait_definition) => (
1004+
CostExprNode::TraitReference(trait_name.clone()),
1005+
StaticCost::ZERO,
1006+
None,
1007+
),
1008+
SymbolicExpressionType::Field(field_identifier) => (
1009+
CostExprNode::FieldIdentifier(field_identifier.clone()),
1010+
StaticCost::ZERO,
1011+
None,
1012+
),
1013+
SymbolicExpressionType::LiteralValue(value) => {
1014+
let cost = calculate_value_cost(value)?;
1015+
// TODO not sure if LiteralValue is needed in the CostExprNode types
1016+
(CostExprNode::AtomValue(value.clone()), cost, None)
1017+
}
1018+
}
1019+
}
9731020
};
9741021

9751022
// Handle special cases for string arguments to functions that include their processing cost
976-
if FUNCTIONS_WITH_ZERO_STRING_ARG_COST.contains(&function_name.as_str()) {
977-
for child in &mut children {
978-
if let CostExprNode::AtomValue(Value::Sequence(SequenceData::String(_))) = &child.expr {
979-
child.cost = StaticCost::ZERO;
1023+
if let Some(function_name) = &function_name_opt {
1024+
if FUNCTIONS_WITH_ZERO_STRING_ARG_COST.contains(&function_name.as_str()) {
1025+
for child in &mut children {
1026+
if let CostExprNode::AtomValue(Value::Sequence(SequenceData::String(_))) =
1027+
&child.expr
1028+
{
1029+
child.cost = StaticCost::ZERO;
1030+
}
9801031
}
9811032
}
9821033
}

clarity/src/vm/tests/analysis.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// TODO: This needs work to get the dynamic vs static testing working
22
use std::collections::HashMap;
3+
use std::path::Path;
34

45
use rstest::rstest;
56
use stacks_common::types::StacksEpochId;
@@ -314,3 +315,55 @@ fn execute_contract_function_and_get_cost(
314315
runtime: final_cost.runtime - initial_cost.runtime,
315316
}
316317
}
318+
319+
#[test]
320+
fn test_pox_4_costs() {
321+
let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
322+
let pox_4_path = workspace_root
323+
.join("contrib")
324+
.join("boot-contracts-unit-tests")
325+
.join("boot_contracts")
326+
.join("pox-4.clar");
327+
let contract_source = std::fs::read_to_string(&pox_4_path)
328+
.unwrap_or_else(|e| panic!("Failed to read pox-4.clar file at {:?}: {}", pox_4_path, e));
329+
330+
let contract_id = QualifiedContractIdentifier::transient();
331+
let epoch = StacksEpochId::Epoch32;
332+
let clarity_version = ClarityVersion::Clarity3;
333+
334+
let ast = crate::vm::ast::build_ast(
335+
&contract_id,
336+
&contract_source,
337+
&mut (),
338+
clarity_version,
339+
epoch,
340+
)
341+
.expect("Failed to build AST from pox-4.clar");
342+
343+
let cost_map = static_cost_from_ast(&ast, &clarity_version)
344+
.expect("Failed to perform static cost analysis on pox-4.clar");
345+
346+
// Check some functions in the cost map
347+
let key_functions = vec![
348+
"stack-stx",
349+
"delegate-stx",
350+
"get-stacker-info",
351+
"current-pox-reward-cycle",
352+
"stack-aggregation-commit",
353+
"stack-increase",
354+
"stack-extend",
355+
];
356+
357+
for function_name in key_functions {
358+
assert!(
359+
cost_map.contains_key(function_name),
360+
"Expected function '{}' to be present in cost map",
361+
function_name
362+
);
363+
364+
let (_cost, _trait_count) = cost_map.get(function_name).expect(&format!(
365+
"Failed to get cost for function '{}'",
366+
function_name
367+
));
368+
}
369+
}

0 commit comments

Comments
 (0)