Skip to content

Commit b933704

Browse files
committed
properly build function definition top-level map
1 parent 86cff1c commit b933704

File tree

2 files changed

+112
-43
lines changed

2 files changed

+112
-43
lines changed

clarity/src/vm/costs/analysis.rs

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use crate::vm::{ClarityVersion, Value};
2020
// type-checking
2121
// lookups
2222
// unwrap evaluates both branches (https://github.com/clarity-lang/reference/issues/59)
23+
// possibly use ContractContext? Enviornment? we need to use this somehow to
24+
// provide full view of a contract, rather than passing in source
2325

2426
const STRING_COST_BASE: u64 = 36;
2527
const STRING_COST_MULTIPLIER: u64 = 3;
@@ -28,6 +30,15 @@ const STRING_COST_MULTIPLIER: u64 = 3;
2830
/// cost includes their processing
2931
const FUNCTIONS_WITH_ZERO_STRING_ARG_COST: &[&str] = &["concat", "len"];
3032

33+
/// Function definition keywords in Clarity
34+
const FUNCTION_DEFINITION_KEYWORDS: &[&str] =
35+
&["define-public", "define-private", "define-read-only"];
36+
37+
/// Check if a function name is a function definition keyword
38+
fn is_function_definition(function_name: &str) -> bool {
39+
FUNCTION_DEFINITION_KEYWORDS.contains(&function_name)
40+
}
41+
3142
#[derive(Debug, Clone)]
3243
pub enum CostExprNode {
3344
// Native Clarity functions
@@ -204,7 +215,7 @@ fn static_cost_native(
204215
let exprs = &ast.expressions;
205216
let user_args = UserArgumentsContext::new();
206217
let expr = &exprs[0];
207-
let cost_analysis_tree =
218+
let (_, cost_analysis_tree) =
208219
build_cost_analysis_tree(&expr, &user_args, cost_map, clarity_version)?;
209220

210221
let summing_cost = calculate_total_cost_with_branching(&cost_analysis_tree);
@@ -225,7 +236,7 @@ pub fn static_cost(
225236
let user_args = UserArgumentsContext::new();
226237
let mut costs = HashMap::new();
227238
for expr in exprs {
228-
let cost_analysis_tree =
239+
let (_, cost_analysis_tree) =
229240
build_cost_analysis_tree(expr, &user_args, &costs, clarity_version)?;
230241

231242
let summing_cost = calculate_total_cost_with_branching(&cost_analysis_tree);
@@ -239,57 +250,71 @@ pub fn static_cost(
239250
Ok(costs)
240251
}
241252

242-
fn build_cost_analysis_tree(
253+
pub fn build_cost_analysis_tree(
243254
expr: &SymbolicExpression,
244255
user_args: &UserArgumentsContext,
245256
cost_map: &HashMap<String, StaticCost>,
246257
clarity_version: &ClarityVersion,
247-
) -> Result<CostAnalysisNode, String> {
258+
) -> Result<(Option<String>, CostAnalysisNode), String> {
248259
match &expr.expr {
249260
SymbolicExpressionType::List(list) => {
250261
if let Some(function_name) = list.first().and_then(|first| first.match_atom()) {
251-
if function_name.as_str() == "define-public"
252-
|| function_name.as_str() == "define-private"
253-
|| function_name.as_str() == "define-read-only"
254-
{
255-
return build_function_definition_cost_analysis_tree(
262+
if is_function_definition(function_name.as_str()) {
263+
let (returned_function_name, cost_analysis_tree) =
264+
build_function_definition_cost_analysis_tree(
265+
list,
266+
user_args,
267+
cost_map,
268+
clarity_version,
269+
)?;
270+
Ok((Some(returned_function_name), cost_analysis_tree))
271+
} else {
272+
let cost_analysis_tree = build_listlike_cost_analysis_tree(
256273
list,
257274
user_args,
258275
cost_map,
259276
clarity_version,
260-
);
277+
)?;
278+
Ok((None, cost_analysis_tree))
261279
}
280+
} else {
281+
let cost_analysis_tree =
282+
build_listlike_cost_analysis_tree(list, user_args, cost_map, clarity_version)?;
283+
Ok((None, cost_analysis_tree))
262284
}
263-
build_listlike_cost_analysis_tree(list, user_args, cost_map, clarity_version)
264285
}
265286
SymbolicExpressionType::AtomValue(value) => {
266287
let cost = calculate_value_cost(value)?;
267-
Ok(CostAnalysisNode::leaf(
268-
CostExprNode::AtomValue(value.clone()),
269-
cost,
288+
Ok((
289+
None,
290+
CostAnalysisNode::leaf(CostExprNode::AtomValue(value.clone()), cost),
270291
))
271292
}
272293
SymbolicExpressionType::LiteralValue(value) => {
273294
let cost = calculate_value_cost(value)?;
274-
Ok(CostAnalysisNode::leaf(
275-
CostExprNode::AtomValue(value.clone()),
276-
cost,
295+
Ok((
296+
None,
297+
CostAnalysisNode::leaf(CostExprNode::AtomValue(value.clone()), cost),
277298
))
278299
}
279300
SymbolicExpressionType::Atom(name) => {
280301
let expr_node = parse_atom_expression(name, user_args)?;
281-
Ok(CostAnalysisNode::leaf(expr_node, StaticCost::ZERO))
302+
Ok((None, CostAnalysisNode::leaf(expr_node, StaticCost::ZERO)))
282303
}
283-
SymbolicExpressionType::Field(field_identifier) => Ok(CostAnalysisNode::leaf(
284-
CostExprNode::FieldIdentifier(field_identifier.clone()),
285-
StaticCost::ZERO,
304+
SymbolicExpressionType::Field(field_identifier) => Ok((
305+
None,
306+
CostAnalysisNode::leaf(
307+
CostExprNode::FieldIdentifier(field_identifier.clone()),
308+
StaticCost::ZERO,
309+
),
286310
)),
287-
SymbolicExpressionType::TraitReference(trait_name, _trait_definition) => {
288-
Ok(CostAnalysisNode::leaf(
311+
SymbolicExpressionType::TraitReference(trait_name, _trait_definition) => Ok((
312+
None,
313+
CostAnalysisNode::leaf(
289314
CostExprNode::TraitReference(trait_name.clone()),
290315
StaticCost::ZERO,
291-
))
292-
}
316+
),
317+
)),
293318
}
294319
}
295320

@@ -316,13 +341,14 @@ fn build_function_definition_cost_analysis_tree(
316341
_user_args: &UserArgumentsContext,
317342
cost_map: &HashMap<String, StaticCost>,
318343
clarity_version: &ClarityVersion,
319-
) -> Result<CostAnalysisNode, String> {
344+
) -> Result<(String, CostAnalysisNode), String> {
320345
let define_type = list[0]
321346
.match_atom()
322347
.ok_or("Expected atom for define type")?;
323348
let signature = list[1]
324349
.match_list()
325350
.ok_or("Expected list for function signature")?;
351+
println!("signature: {:?}", signature);
326352
let body = &list[2];
327353

328354
let mut children = Vec::new();
@@ -360,14 +386,23 @@ fn build_function_definition_cost_analysis_tree(
360386
}
361387

362388
// Process the function body with the function's user arguments context
363-
let body_tree = build_cost_analysis_tree(body, &function_user_args, cost_map, clarity_version)?;
389+
let (_, body_tree) =
390+
build_cost_analysis_tree(body, &function_user_args, cost_map, clarity_version)?;
364391
children.push(body_tree);
365392

393+
// Get the function name from the signature
394+
let function_name = signature[0]
395+
.match_atom()
396+
.ok_or("Expected atom for function name")?;
397+
366398
// Create the function definition node with zero cost (function definitions themselves don't have execution cost)
367-
Ok(CostAnalysisNode::new(
368-
CostExprNode::UserFunction(define_type.clone()),
369-
StaticCost::ZERO,
370-
children,
399+
Ok((
400+
function_name.clone().to_string(),
401+
CostAnalysisNode::new(
402+
CostExprNode::UserFunction(define_type.clone()),
403+
StaticCost::ZERO,
404+
children,
405+
),
371406
))
372407
}
373408

@@ -389,12 +424,8 @@ fn build_listlike_cost_analysis_tree(
389424

390425
// Build children for all exprs
391426
for expr in exprs[1..].iter() {
392-
children.push(build_cost_analysis_tree(
393-
expr,
394-
user_args,
395-
cost_map,
396-
clarity_version,
397-
)?);
427+
let (_, child_tree) = build_cost_analysis_tree(expr, user_args, cost_map, clarity_version)?;
428+
children.push(child_tree);
398429
}
399430

400431
let function_name = get_function_name(&exprs[0])?;
@@ -826,7 +857,7 @@ mod tests {
826857
let expr = &ast.expressions[0];
827858
let user_args = UserArgumentsContext::new();
828859
let cost_map = HashMap::new(); // Empty cost map for tests
829-
let cost_tree =
860+
let (_, cost_tree) =
830861
build_cost_analysis_tree(expr, &user_args, &cost_map, &ClarityVersion::Clarity3)
831862
.unwrap();
832863

@@ -873,7 +904,7 @@ mod tests {
873904
let expr = &ast.expressions[0];
874905
let user_args = UserArgumentsContext::new();
875906
let cost_map = HashMap::new(); // Empty cost map for tests
876-
let cost_tree =
907+
let (_, cost_tree) =
877908
build_cost_analysis_tree(expr, &user_args, &cost_map, &ClarityVersion::Clarity3)
878909
.unwrap();
879910

@@ -906,7 +937,7 @@ mod tests {
906937
let expr = &ast.expressions[0];
907938
let user_args = UserArgumentsContext::new();
908939
let cost_map = HashMap::new(); // Empty cost map for tests
909-
let cost_tree =
940+
let (_, cost_tree) =
910941
build_cost_analysis_tree(expr, &user_args, &cost_map, &ClarityVersion::Clarity3)
911942
.unwrap();
912943

@@ -929,7 +960,7 @@ mod tests {
929960
let expr = &ast.expressions[0];
930961
let user_args = UserArgumentsContext::new();
931962
let cost_map = HashMap::new(); // Empty cost map for tests
932-
let cost_tree =
963+
let (_, cost_tree) =
933964
build_cost_analysis_tree(expr, &user_args, &cost_map, &ClarityVersion::Clarity3)
934965
.unwrap();
935966

clarity/src/vm/tests/analysis.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17+
use std::collections::HashMap;
18+
1719
use rstest::rstest;
1820
use stacks_common::types::StacksEpochId;
1921

2022
use crate::vm::contexts::OwnedEnvironment;
21-
use crate::vm::costs::analysis::static_cost;
23+
use crate::vm::costs::analysis::{build_cost_analysis_tree, static_cost, UserArgumentsContext};
2224
use crate::vm::costs::ExecutionCost;
2325
use crate::vm::tests::{tl_env_factory, TopLevelMemoryEnvironmentGenerator};
2426
use crate::vm::types::{PrincipalData, QualifiedContractIdentifier};
25-
use crate::vm::ClarityVersion;
27+
use crate::vm::{ast, ClarityVersion};
2628

2729
const SIMPLE_TRAIT_SRC: &str = r#"(define-trait mytrait (
2830
(somefunc (uint uint) (response uint uint))
@@ -176,3 +178,39 @@ fn test_complex_trait_implementation_costs(
176178
}
177179
}
178180
}
181+
182+
#[test]
183+
fn test_build_cost_analysis_tree_function_definition() {
184+
let source = r#"(define-public (somefunc (a uint))
185+
(ok (+ a 1))
186+
)"#;
187+
188+
let contract_id = QualifiedContractIdentifier::transient();
189+
let ast = ast::parse(
190+
&contract_id,
191+
source,
192+
ClarityVersion::Clarity3,
193+
StacksEpochId::Epoch32,
194+
)
195+
.expect("Failed to parse source code");
196+
197+
let expr = &ast[0];
198+
let user_args = UserArgumentsContext::new();
199+
let cost_map = HashMap::new();
200+
201+
let clarity_version = ClarityVersion::Clarity3;
202+
let result = build_cost_analysis_tree(expr, &user_args, &cost_map, &clarity_version);
203+
204+
match result {
205+
Ok((function_name, node)) => {
206+
assert_eq!(function_name, Some("somefunc".to_string()));
207+
assert!(matches!(
208+
node.expr,
209+
crate::vm::costs::analysis::CostExprNode::UserFunction(_)
210+
));
211+
}
212+
Err(e) => {
213+
panic!("Expected Ok result, got error: {}", e);
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)