Skip to content

Commit d452a3a

Browse files
committed
Merge branch 'develop' into feat/epoch-3.3-tasks
2 parents 35587d1 + 6a11cf5 commit d452a3a

20 files changed

+2184
-125
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
2020
- Renamed Clarity 4's new `block-time` to `stacks-block-time`
2121
- Improve cost-tracking for type-checking function arguments in epoch 3.3 (see [#6425](https://github.com/stacks-network/stacks-core/issues/6425))
2222
- Replaced `libsecp256k1` with `k256` and `p256` from RustCrypto and removed separate Wasm implementations.
23+
- Added limits in the type-checker for the number of parameters in functions (maximum 256), and the number of methods in traits (maximum 256). These limits are enforced starting in Epoch 3.3.
2324

2425
## [3.2.0.0.2]
2526

clarity-types/src/errors/analysis.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ pub enum CheckErrors {
280280
DefaultTypesMustMatch(Box<TypeSignature>, Box<TypeSignature>),
281281
IllegalOrUnknownFunctionApplication(String),
282282
UnknownFunction(String),
283+
TooManyFunctionParameters(usize, usize),
283284

284285
// traits
285286
NoSuchTrait(String, String),
@@ -294,6 +295,7 @@ pub enum CheckErrors {
294295
TraitBasedContractCallInReadOnly,
295296
ContractOfExpectsTrait,
296297
IncompatibleTrait(Box<TraitIdentifier>, Box<TraitIdentifier>),
298+
TraitTooManyMethods(usize, usize),
297299

298300
// strings
299301
InvalidCharactersDetected,
@@ -587,6 +589,7 @@ impl DiagnosableError for CheckErrors {
587589
CheckErrors::DefaultTypesMustMatch(type_1, type_2) => format!("expression types passed in 'default-to' must match (got '{type_1}' and '{type_2}')"),
588590
CheckErrors::IllegalOrUnknownFunctionApplication(function_name) => format!("use of illegal / unresolved function '{function_name}"),
589591
CheckErrors::UnknownFunction(function_name) => format!("use of unresolved function '{function_name}'"),
592+
CheckErrors::TooManyFunctionParameters(found, allowed) => format!("too many function parameters specified: found {found}, the maximum is {allowed}"),
590593
CheckErrors::TraitBasedContractCallInReadOnly => "use of trait based contract calls are not allowed in read-only context".into(),
591594
CheckErrors::WriteAttemptedInReadOnly => "expecting read-only statements, detected a writing operation".into(),
592595
CheckErrors::AtBlockClosureMustBeReadOnly => "(at-block ...) closures expect read-only statements, but detected a writing operation".into(),
@@ -605,6 +608,7 @@ impl DiagnosableError for CheckErrors {
605608
CheckErrors::TraitReferenceNotAllowed => "trait references can not be stored".into(),
606609
CheckErrors::ContractOfExpectsTrait => "trait reference expected".into(),
607610
CheckErrors::IncompatibleTrait(expected_trait, actual_trait) => format!("trait '{actual_trait}' is not a compatible with expected trait, '{expected_trait}'"),
611+
CheckErrors::TraitTooManyMethods(found, allowed) => format!("too many trait methods specified: found {found}, the maximum is {allowed}"),
608612
CheckErrors::InvalidCharactersDetected => "invalid characters detected".into(),
609613
CheckErrors::InvalidUTF8Encoding => "invalid UTF8 encoding".into(),
610614
CheckErrors::InvalidSecp65k1Signature => "invalid seckp256k1 signature".into(),

clarity/src/vm/analysis/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub fn mem_type_check(
5555
) -> Result<(Option<TypeSignature>, ContractAnalysis), CheckError> {
5656
let contract_identifier = QualifiedContractIdentifier::transient();
5757
let contract = build_ast(&contract_identifier, snippet, &mut (), version, epoch)
58-
.map_err(|_| CheckErrors::Expects("Failed to build AST".into()))?
58+
.map_err(|e| CheckErrors::Expects(format!("Failed to build AST: {e}")))?
5959
.expressions;
6060

6161
let mut marf = MemoryBackingStore::new();

clarity/src/vm/analysis/type_checker/v2_1/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ use crate::vm::ClarityVersion;
5757
#[cfg(test)]
5858
pub mod tests;
5959

60+
/// The maximum number of parameters a function definition can have.
61+
/// This limit is enforced starting in Epoch 3.3.
62+
pub const MAX_FUNCTION_PARAMETERS: usize = 256;
63+
/// The maximum number of methods a trait definition can have.
64+
/// This limit is enforced starting in Epoch 3.3.
65+
pub const MAX_TRAIT_METHODS: usize = 256;
66+
6067
/*
6168
6269
Type-checking in our language is achieved through a single-direction inference.
@@ -1293,6 +1300,15 @@ impl<'a, 'b> TypeChecker<'a, 'b> {
12931300
let (function_name, args) = signature
12941301
.split_first()
12951302
.ok_or(CheckErrors::RequiresAtLeastArguments(1, 0))?;
1303+
1304+
if self.epoch.limits_parameter_and_method_count() && args.len() > MAX_FUNCTION_PARAMETERS {
1305+
return Err(CheckErrors::TooManyFunctionParameters(
1306+
args.len(),
1307+
MAX_FUNCTION_PARAMETERS,
1308+
)
1309+
.into());
1310+
}
1311+
12961312
let function_name = function_name
12971313
.match_atom()
12981314
.ok_or(CheckErrors::BadFunctionName)?;
@@ -1677,7 +1693,7 @@ impl<'a, 'b> TypeChecker<'a, 'b> {
16771693
let trait_signature = TypeSignature::parse_trait_type_repr(
16781694
function_types,
16791695
&mut (),
1680-
StacksEpochId::Epoch21,
1696+
self.epoch,
16811697
self.clarity_version,
16821698
)?;
16831699

clarity/src/vm/analysis/type_checker/v2_1/tests/mod.rs

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
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 clarity_types::token::Token;
1718
use clarity_types::types::SequenceSubtype;
1819
#[cfg(test)]
1920
use rstest::rstest;
@@ -23,6 +24,7 @@ use stacks_common::types::StacksEpochId;
2324

2425
use crate::vm::analysis::errors::{CheckError, CheckErrors, SyntaxBindingError};
2526
use crate::vm::analysis::mem_type_check as mem_run_analysis;
27+
use crate::vm::analysis::type_checker::v2_1::{MAX_FUNCTION_PARAMETERS, MAX_TRAIT_METHODS};
2628
use crate::vm::analysis::types::ContractAnalysis;
2729
use crate::vm::ast::build_ast;
2830
use crate::vm::ast::errors::ParseErrors;
@@ -71,13 +73,13 @@ fn type_check_helper(exp: &str) -> Result<TypeSignature, CheckError> {
7173
fn type_check_helper_version(
7274
exp: &str,
7375
version: ClarityVersion,
76+
epoch: StacksEpochId,
7477
) -> Result<TypeSignature, CheckError> {
75-
mem_run_analysis(exp, version, StacksEpochId::latest())
76-
.map(|(type_sig_opt, _)| type_sig_opt.unwrap())
78+
mem_run_analysis(exp, version, epoch).map(|(type_sig_opt, _)| type_sig_opt.unwrap())
7779
}
7880

7981
fn type_check_helper_v1(exp: &str) -> Result<TypeSignature, CheckError> {
80-
type_check_helper_version(exp, ClarityVersion::Clarity1)
82+
type_check_helper_version(exp, ClarityVersion::Clarity1, StacksEpochId::latest())
8183
}
8284

8385
fn buff_type(size: u32) -> TypeSignature {
@@ -286,7 +288,12 @@ fn test_get_block_info() {
286288
expected,
287289
&format!(
288290
"{}",
289-
type_check_helper_version(good_test, ClarityVersion::Clarity2).unwrap()
291+
type_check_helper_version(
292+
good_test,
293+
ClarityVersion::Clarity2,
294+
StacksEpochId::latest()
295+
)
296+
.unwrap()
290297
)
291298
);
292299
}
@@ -295,15 +302,20 @@ fn test_get_block_info() {
295302
expected_v210,
296303
&format!(
297304
"{}",
298-
type_check_helper_version(good_test_v210, ClarityVersion::Clarity2).unwrap()
305+
type_check_helper_version(
306+
good_test_v210,
307+
ClarityVersion::Clarity2,
308+
StacksEpochId::latest()
309+
)
310+
.unwrap()
299311
)
300312
);
301313
}
302314

303315
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
304316
assert_eq!(
305317
*expected,
306-
*type_check_helper_version(bad_test, ClarityVersion::Clarity2)
318+
*type_check_helper_version(bad_test, ClarityVersion::Clarity2, StacksEpochId::latest())
307319
.unwrap_err()
308320
.err
309321
);
@@ -354,6 +366,71 @@ fn test_get_burn_block_info() {
354366
}
355367
}
356368

369+
#[apply(test_clarity_versions)]
370+
fn test_define_functions(#[case] version: ClarityVersion, #[case] epoch: StacksEpochId) {
371+
let good = [
372+
"(define-private (foo (a uint) (b int)) true)",
373+
"(define-public (bar (x (buff 32))) (ok x))",
374+
"(define-read-only (baz (p principal)) p)",
375+
];
376+
377+
for good_test in good.iter() {
378+
mem_type_check(good_test).unwrap();
379+
}
380+
381+
// Tests that fail only after epoch 3.3
382+
let bad = [
383+
format!(
384+
"(define-private (foo {}) true)",
385+
(0..(MAX_FUNCTION_PARAMETERS + 1))
386+
.map(|i| format!("(param-{} uint)", i))
387+
.collect::<Vec<String>>()
388+
.join(" ")
389+
),
390+
format!(
391+
"(define-public (foo {}) (ok true))",
392+
(0..(MAX_FUNCTION_PARAMETERS + 1))
393+
.map(|i| format!("(param-{} uint)", i))
394+
.collect::<Vec<String>>()
395+
.join(" ")
396+
),
397+
format!(
398+
"(define-read-only (foo {}) true)",
399+
(0..(MAX_FUNCTION_PARAMETERS + 1))
400+
.map(|i| format!("(param-{} uint)", i))
401+
.collect::<Vec<String>>()
402+
.join(" ")
403+
),
404+
];
405+
let bad_expected = [
406+
CheckErrors::TooManyFunctionParameters(
407+
MAX_FUNCTION_PARAMETERS + 1,
408+
MAX_FUNCTION_PARAMETERS,
409+
),
410+
CheckErrors::TooManyFunctionParameters(
411+
MAX_FUNCTION_PARAMETERS + 1,
412+
MAX_FUNCTION_PARAMETERS,
413+
),
414+
CheckErrors::TooManyFunctionParameters(
415+
MAX_FUNCTION_PARAMETERS + 1,
416+
MAX_FUNCTION_PARAMETERS,
417+
),
418+
];
419+
420+
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
421+
if epoch.limits_parameter_and_method_count() {
422+
assert_eq!(
423+
*expected,
424+
*type_check_helper_version(bad_test, version, epoch)
425+
.unwrap_err()
426+
.err
427+
);
428+
} else {
429+
mem_run_analysis(bad_test, version, epoch).unwrap();
430+
}
431+
}
432+
}
433+
357434
#[apply(test_clarity_versions)]
358435
fn test_define_trait(#[case] version: ClarityVersion, #[case] epoch: StacksEpochId) {
359436
let good = [
@@ -371,31 +448,80 @@ fn test_define_trait(#[case] version: ClarityVersion, #[case] epoch: StacksEpoch
371448
"(define-trait trait-1 ((get-1 uint uint)))",
372449
"(define-trait trait-1 ((get-1 (uint) (uint))))",
373450
"(define-trait trait-1 ((get-1 (response uint uint))))",
374-
"(define-trait trait-1)",
375-
"(define-trait)",
451+
"(define-trait trait-1 ((get-1 (uint) (response uint uint)) u1))",
376452
];
377453
let bad_expected = [
378454
CheckErrors::InvalidTypeDescription,
379455
CheckErrors::DefineTraitBadSignature,
380456
CheckErrors::DefineTraitBadSignature,
381457
CheckErrors::InvalidTypeDescription,
458+
CheckErrors::DefineTraitBadSignature,
382459
];
383460

384461
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
385462
assert_eq!(*expected, *type_check_helper(bad_test).unwrap_err().err);
386463
}
387464

388-
let bad = ["(define-trait trait-1)", "(define-trait)"];
465+
// Tests that fail before type-checker
466+
let bad = [
467+
"(define-trait trait-1)",
468+
"(define-trait)",
469+
"(define-trait trait-1 ((get-1 (uint) (response uint uint)))) u1)",
470+
];
389471
let bad_expected = [
390472
ParseErrors::DefineTraitBadSignature,
391473
ParseErrors::DefineTraitBadSignature,
474+
if epoch == StacksEpochId::Epoch20 || epoch == StacksEpochId::Epoch2_05 {
475+
// the pre-2.1 parser returns less instructive errors
476+
ParseErrors::ClosingParenthesisUnexpected
477+
} else {
478+
ParseErrors::UnexpectedToken(Token::Rparen)
479+
},
392480
];
393481

394482
let contract_identifier = QualifiedContractIdentifier::transient();
395483
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
396484
let res = build_ast(&contract_identifier, bad_test, &mut (), version, epoch).unwrap_err();
397485
assert_eq!(*expected, *res.err);
398486
}
487+
488+
// Tests that fail only after epoch 3.3
489+
let bad = [
490+
format!(
491+
"(define-trait trait-1 ({}))",
492+
(0..(MAX_TRAIT_METHODS + 1))
493+
.map(|i| format!("(method-{} (uint) (response uint uint))", i))
494+
.collect::<Vec<String>>()
495+
.join(" ")
496+
),
497+
format!(
498+
"(define-trait trait-1 ((method ({}) (response uint uint))))",
499+
(0..(MAX_FUNCTION_PARAMETERS + 1))
500+
.map(|i| "uint".to_string())
501+
.collect::<Vec<String>>()
502+
.join(" ")
503+
),
504+
];
505+
let bad_expected = [
506+
CheckErrors::TraitTooManyMethods(MAX_TRAIT_METHODS + 1, MAX_TRAIT_METHODS),
507+
CheckErrors::TooManyFunctionParameters(
508+
MAX_FUNCTION_PARAMETERS + 1,
509+
MAX_FUNCTION_PARAMETERS,
510+
),
511+
];
512+
513+
for (bad_test, expected) in bad.iter().zip(bad_expected.iter()) {
514+
if epoch.limits_parameter_and_method_count() {
515+
assert_eq!(
516+
*expected,
517+
*type_check_helper_version(bad_test, version, epoch)
518+
.unwrap_err()
519+
.err
520+
);
521+
} else {
522+
mem_run_analysis(bad_test, version, epoch).unwrap();
523+
}
524+
}
399525
}
400526

401527
#[apply(test_clarity_versions)]

0 commit comments

Comments
 (0)