Skip to content

Commit 3e7ecdb

Browse files
authored
Merge pull request #6606 from obycode/feat/clarity-4-proptests
Proptests for Clarity 4 functions
2 parents 24e3ace + 5635f88 commit 3e7ecdb

File tree

7 files changed

+1892
-43
lines changed

7 files changed

+1892
-43
lines changed

clarity/src/vm/contexts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub enum AssetMapEntry {
8989
The AssetMap is used to track which assets have been transfered from whom
9090
during the execution of a transaction.
9191
*/
92-
#[derive(Debug, Clone)]
92+
#[derive(Debug, Clone, PartialEq, Eq)]
9393
pub struct AssetMap {
9494
/// Sum of all STX transfers by principal
9595
stx_map: HashMap<PrincipalData, u128>,

clarity/src/vm/mod.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -513,23 +513,27 @@ pub fn execute_on_network(program: &str, use_mainnet: bool) -> Result<Option<Val
513513
epoch_205_result
514514
}
515515

516-
/// Runs `program` in a test environment with the provided parameters.
516+
/// Runs `program` in a test environment with the provided parameters and calls
517+
/// the provided functions before and after execution.
517518
#[cfg(any(test, feature = "testing"))]
518-
pub fn execute_with_parameters_and_call_in_global_context<F>(
519+
pub fn execute_with_parameters_and_call_in_global_context<F, G>(
519520
program: &str,
520521
clarity_version: ClarityVersion,
521522
epoch: StacksEpochId,
522523
use_mainnet: bool,
523-
mut global_context_function: F,
524+
sender: clarity_types::types::StandardPrincipalData,
525+
mut before_function: F,
526+
mut after_function: G,
524527
) -> Result<Option<Value>>
525528
where
526529
F: FnMut(&mut GlobalContext) -> Result<()>,
530+
G: FnMut(&mut GlobalContext) -> Result<()>,
527531
{
528532
use crate::vm::database::MemoryBackingStore;
529533
use crate::vm::tests::test_only_mainnet_to_chain_id;
530534
use crate::vm::types::QualifiedContractIdentifier;
531535

532-
let contract_id = QualifiedContractIdentifier::transient();
536+
let contract_id = QualifiedContractIdentifier::new(sender, "contract".into());
533537
let mut contract_context = ContractContext::new(contract_id.clone(), clarity_version);
534538
let mut marf = MemoryBackingStore::new();
535539
let conn = marf.as_clarity_db();
@@ -542,10 +546,12 @@ where
542546
epoch,
543547
);
544548
global_context.execute(|g| {
545-
global_context_function(g)?;
549+
before_function(g)?;
546550
let parsed =
547551
ast::build_ast(&contract_id, program, &mut (), clarity_version, epoch)?.expressions;
548-
eval_all(&parsed, &mut contract_context, g, None)
552+
let res = eval_all(&parsed, &mut contract_context, g, None);
553+
after_function(g)?;
554+
res
549555
})
550556
}
551557

@@ -561,6 +567,8 @@ pub fn execute_with_parameters(
561567
clarity_version,
562568
epoch,
563569
use_mainnet,
570+
clarity_types::types::StandardPrincipalData::transient(),
571+
|_| Ok(()),
564572
|_| Ok(()),
565573
)
566574
}
@@ -593,10 +601,12 @@ pub fn execute_with_limited_execution_time(
593601
ClarityVersion::Clarity1,
594602
StacksEpochId::Epoch20,
595603
false,
604+
clarity_types::types::StandardPrincipalData::transient(),
596605
|g| {
597606
g.set_max_execution_time(max_execution_time);
598607
Ok(())
599608
},
609+
|_| Ok(()),
600610
)
601611
}
602612

clarity/src/vm/tests/conversions.rs

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
use clarity_types::types::MAX_TO_ASCII_BUFFER_LEN;
18+
use proptest::prelude::*;
1819
use stacks_common::types::StacksEpochId;
1920

2021
pub use crate::vm::analysis::errors::CheckErrors;
22+
use crate::vm::tests::proptest_utils::{
23+
contract_name_strategy, execute_versioned, standard_principal_strategy,
24+
to_ascii_buffer_snippet_strategy, utf8_string_ascii_only_snippet_strategy,
25+
utf8_string_snippet_strategy,
26+
};
2127
use crate::vm::tests::test_clarity_versions;
2228
use crate::vm::types::SequenceSubtype::BufferType;
2329
use crate::vm::types::TypeSignature::SequenceType;
@@ -585,7 +591,147 @@ fn test_to_ascii(version: ClarityVersion, epoch: StacksEpochId) {
585591
"(to-ascii? 0x{})",
586592
"ff".repeat(MAX_TO_ASCII_BUFFER_LEN as usize + 1)
587593
);
588-
let result = execute_with_parameters(response_to_ascii, version, epoch, false);
594+
let result = execute_with_parameters(&oversized_buffer_to_ascii, version, epoch, false);
589595
// This should fail at analysis time since the value is too big
590596
assert!(result.is_err());
591597
}
598+
599+
fn evaluate_to_ascii(snippet: &str) -> Value {
600+
execute_versioned(snippet, ClarityVersion::Clarity4)
601+
.unwrap_or_else(|e| panic!("Execution failed for snippet `{snippet}`: {e:?}"))
602+
.unwrap_or_else(|| panic!("Execution returned no value for snippet `{snippet}`"))
603+
}
604+
605+
proptest! {
606+
#[test]
607+
fn prop_to_ascii_from_ints(int_value in any::<i128>()) {
608+
let snippet = format!("(to-ascii? {int_value})");
609+
let evaluation = evaluate_to_ascii(&snippet);
610+
611+
let expected_inner = Value::string_ascii_from_bytes(int_value.to_string().into_bytes())
612+
.expect("int string should be valid ASCII");
613+
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");
614+
615+
prop_assert_eq!(expected, evaluation);
616+
}
617+
618+
#[test]
619+
fn prop_to_ascii_from_uints(uint_value in any::<u128>()) {
620+
let snippet = format!("(to-ascii? u{uint_value})");
621+
let evaluation = evaluate_to_ascii(&snippet);
622+
623+
let expected_inner = Value::string_ascii_from_bytes(format!("u{uint_value}").into_bytes())
624+
.expect("uint string should be valid ASCII");
625+
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");
626+
627+
prop_assert_eq!(expected, evaluation);
628+
}
629+
630+
#[test]
631+
fn prop_to_ascii_from_bools(bool_value in any::<bool>()) {
632+
let literal = if bool_value { "true" } else { "false" };
633+
let snippet = format!("(to-ascii? {literal})");
634+
let evaluation = evaluate_to_ascii(&snippet);
635+
636+
let expected_inner = Value::string_ascii_from_bytes(literal.as_bytes().to_vec())
637+
.expect("bool string should be valid ASCII");
638+
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");
639+
640+
prop_assert_eq!(expected, evaluation);
641+
}
642+
643+
#[test]
644+
fn prop_to_ascii_from_standard_principals(principal in standard_principal_strategy()) {
645+
let literal = format!("'{}", principal);
646+
let snippet = format!("(to-ascii? {literal})");
647+
let evaluation = evaluate_to_ascii(&snippet);
648+
649+
let expected_inner = Value::string_ascii_from_bytes(principal.to_string().into_bytes())
650+
.expect("principal string should be valid ASCII");
651+
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");
652+
653+
prop_assert_eq!(expected, evaluation);
654+
}
655+
656+
#[test]
657+
fn prop_to_ascii_from_contract_principals(
658+
issuer in standard_principal_strategy(),
659+
contract_name in contract_name_strategy(),
660+
) {
661+
let contract_name_str = contract_name.to_string();
662+
let literal = format!("'{}.{}", issuer, contract_name_str);
663+
let snippet = format!("(to-ascii? {literal})");
664+
let evaluation = evaluate_to_ascii(&snippet);
665+
666+
let expected_inner = Value::string_ascii_from_bytes(
667+
format!("{}.{}", issuer, contract_name_str).into_bytes()
668+
)
669+
.expect("contract principal string should be valid ASCII");
670+
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");
671+
672+
prop_assert_eq!(expected, evaluation);
673+
}
674+
675+
#[test]
676+
fn prop_to_ascii_from_buffers(buffer in to_ascii_buffer_snippet_strategy()) {
677+
let snippet = format!("(to-ascii? {buffer})");
678+
let evaluation = evaluate_to_ascii(&snippet);
679+
680+
let expected_inner = Value::string_ascii_from_bytes(buffer.to_string().into_bytes())
681+
.expect("buffer string should be valid ASCII");
682+
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");
683+
684+
prop_assert_eq!(expected, evaluation);
685+
}
686+
687+
#[test]
688+
fn prop_to_ascii_from_ascii_utf8_strings(utf8_string in utf8_string_ascii_only_snippet_strategy()) {
689+
let snippet = format!("(to-ascii? {utf8_string})");
690+
let evaluation = evaluate_to_ascii(&snippet);
691+
692+
let ascii_snippet = &utf8_string[1..]; // Remove the u prefix
693+
let expected_inner = execute_versioned(ascii_snippet, ClarityVersion::Clarity4)
694+
.unwrap_or_else(|e| panic!("Execution failed for `{ascii_snippet}`: {e:?}"))
695+
.unwrap_or_else(|| panic!("Execution returned no value for `{ascii_snippet}`"));
696+
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");
697+
698+
prop_assert_eq!(expected, evaluation);
699+
}
700+
701+
#[test]
702+
fn prop_to_ascii_from_utf8_strings(utf8_string in utf8_string_snippet_strategy()) {
703+
let snippet = format!("(to-ascii? {utf8_string})");
704+
let evaluation = evaluate_to_ascii(&snippet);
705+
706+
let literal_value = execute_versioned(&utf8_string, ClarityVersion::Clarity4)
707+
.unwrap_or_else(|e| panic!("Execution failed for literal `{utf8_string}`: {e:?}"))
708+
.unwrap_or_else(|| panic!("Execution returned no value for literal `{utf8_string}`"));
709+
710+
let utf8_chars = match &literal_value {
711+
Value::Sequence(SequenceData::String(CharType::UTF8(data))) => data.data.clone(),
712+
_ => panic!("Expected UTF-8 string literal, got `{literal_value:?}`"),
713+
};
714+
let is_ascii = utf8_chars
715+
.iter()
716+
.all(|char_bytes| char_bytes.len() == 1 && char_bytes[0].is_ascii());
717+
718+
if is_ascii {
719+
let ascii_bytes: Vec<u8> = utf8_chars
720+
.iter()
721+
.map(|char_bytes| char_bytes[0])
722+
.collect();
723+
match Value::string_ascii_from_bytes(ascii_bytes) {
724+
Ok(expected_inner) => {
725+
let expected = Value::okay(expected_inner)
726+
.expect("response wrapping should succeed");
727+
prop_assert_eq!(expected, evaluation);
728+
}
729+
Err(_) => {
730+
prop_assert_eq!(Value::err_uint(1), evaluation);
731+
}
732+
}
733+
} else {
734+
prop_assert_eq!(Value::err_uint(1), evaluation);
735+
}
736+
}
737+
}

0 commit comments

Comments
 (0)