Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clarity/src/vm/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub enum AssetMapEntry {
The AssetMap is used to track which assets have been transfered from whom
during the execution of a transaction.
*/
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AssetMap {
/// Sum of all STX transfers by principal
stx_map: HashMap<PrincipalData, u128>,
Expand Down
22 changes: 16 additions & 6 deletions clarity/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,23 +513,27 @@ pub fn execute_on_network(program: &str, use_mainnet: bool) -> Result<Option<Val
epoch_205_result
}

/// Runs `program` in a test environment with the provided parameters.
/// Runs `program` in a test environment with the provided parameters and calls
/// the provided functions before and after execution.
#[cfg(any(test, feature = "testing"))]
pub fn execute_with_parameters_and_call_in_global_context<F>(
pub fn execute_with_parameters_and_call_in_global_context<F, G>(
program: &str,
clarity_version: ClarityVersion,
epoch: StacksEpochId,
use_mainnet: bool,
mut global_context_function: F,
sender: clarity_types::types::StandardPrincipalData,
mut before_function: F,
mut after_function: G,
) -> Result<Option<Value>>
where
F: FnMut(&mut GlobalContext) -> Result<()>,
G: FnMut(&mut GlobalContext) -> Result<()>,
{
use crate::vm::database::MemoryBackingStore;
use crate::vm::tests::test_only_mainnet_to_chain_id;
use crate::vm::types::QualifiedContractIdentifier;

let contract_id = QualifiedContractIdentifier::transient();
let contract_id = QualifiedContractIdentifier::new(sender, "contract".into());
let mut contract_context = ContractContext::new(contract_id.clone(), clarity_version);
let mut marf = MemoryBackingStore::new();
let conn = marf.as_clarity_db();
Expand All @@ -542,10 +546,12 @@ where
epoch,
);
global_context.execute(|g| {
global_context_function(g)?;
before_function(g)?;
let parsed =
ast::build_ast(&contract_id, program, &mut (), clarity_version, epoch)?.expressions;
eval_all(&parsed, &mut contract_context, g, None)
let res = eval_all(&parsed, &mut contract_context, g, None);
after_function(g)?;
res
})
}

Expand All @@ -561,6 +567,8 @@ pub fn execute_with_parameters(
clarity_version,
epoch,
use_mainnet,
clarity_types::types::StandardPrincipalData::transient(),
|_| Ok(()),
|_| Ok(()),
)
}
Expand Down Expand Up @@ -593,10 +601,12 @@ pub fn execute_with_limited_execution_time(
ClarityVersion::Clarity1,
StacksEpochId::Epoch20,
false,
clarity_types::types::StandardPrincipalData::transient(),
|g| {
g.set_max_execution_time(max_execution_time);
Ok(())
},
|_| Ok(()),
)
}

Expand Down
148 changes: 147 additions & 1 deletion clarity/src/vm/tests/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use clarity_types::types::MAX_TO_ASCII_BUFFER_LEN;
use proptest::prelude::*;
use stacks_common::types::StacksEpochId;

pub use crate::vm::analysis::errors::CheckErrors;
use crate::vm::tests::proptest_utils::{
contract_name_strategy, execute_versioned, standard_principal_strategy,
to_ascii_buffer_snippet_strategy, utf8_string_ascii_only_snippet_strategy,
utf8_string_snippet_strategy,
};
use crate::vm::tests::test_clarity_versions;
use crate::vm::types::SequenceSubtype::BufferType;
use crate::vm::types::TypeSignature::SequenceType;
Expand Down Expand Up @@ -585,7 +591,147 @@ fn test_to_ascii(version: ClarityVersion, epoch: StacksEpochId) {
"(to-ascii? 0x{})",
"ff".repeat(MAX_TO_ASCII_BUFFER_LEN as usize + 1)
);
let result = execute_with_parameters(response_to_ascii, version, epoch, false);
let result = execute_with_parameters(&oversized_buffer_to_ascii, version, epoch, false);
// This should fail at analysis time since the value is too big
assert!(result.is_err());
}

fn evaluate_to_ascii(snippet: &str) -> Value {
execute_versioned(snippet, ClarityVersion::Clarity4)
.unwrap_or_else(|e| panic!("Execution failed for snippet `{snippet}`: {e:?}"))
.unwrap_or_else(|| panic!("Execution returned no value for snippet `{snippet}`"))
}

proptest! {
#[test]
fn prop_to_ascii_from_ints(int_value in any::<i128>()) {
let snippet = format!("(to-ascii? {int_value})");
let evaluation = evaluate_to_ascii(&snippet);

let expected_inner = Value::string_ascii_from_bytes(int_value.to_string().into_bytes())
.expect("int string should be valid ASCII");
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");

prop_assert_eq!(expected, evaluation);
}

#[test]
fn prop_to_ascii_from_uints(uint_value in any::<u128>()) {
let snippet = format!("(to-ascii? u{uint_value})");
let evaluation = evaluate_to_ascii(&snippet);

let expected_inner = Value::string_ascii_from_bytes(format!("u{uint_value}").into_bytes())
.expect("uint string should be valid ASCII");
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");

prop_assert_eq!(expected, evaluation);
}

#[test]
fn prop_to_ascii_from_bools(bool_value in any::<bool>()) {
let literal = if bool_value { "true" } else { "false" };
let snippet = format!("(to-ascii? {literal})");
let evaluation = evaluate_to_ascii(&snippet);

let expected_inner = Value::string_ascii_from_bytes(literal.as_bytes().to_vec())
.expect("bool string should be valid ASCII");
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");

prop_assert_eq!(expected, evaluation);
}

#[test]
fn prop_to_ascii_from_standard_principals(principal in standard_principal_strategy()) {
let literal = format!("'{}", principal);
let snippet = format!("(to-ascii? {literal})");
let evaluation = evaluate_to_ascii(&snippet);

let expected_inner = Value::string_ascii_from_bytes(principal.to_string().into_bytes())
.expect("principal string should be valid ASCII");
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");

prop_assert_eq!(expected, evaluation);
}

#[test]
fn prop_to_ascii_from_contract_principals(
issuer in standard_principal_strategy(),
contract_name in contract_name_strategy(),
) {
let contract_name_str = contract_name.to_string();
let literal = format!("'{}.{}", issuer, contract_name_str);
let snippet = format!("(to-ascii? {literal})");
let evaluation = evaluate_to_ascii(&snippet);

let expected_inner = Value::string_ascii_from_bytes(
format!("{}.{}", issuer, contract_name_str).into_bytes()
)
.expect("contract principal string should be valid ASCII");
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");

prop_assert_eq!(expected, evaluation);
}

#[test]
fn prop_to_ascii_from_buffers(buffer in to_ascii_buffer_snippet_strategy()) {
let snippet = format!("(to-ascii? {buffer})");
let evaluation = evaluate_to_ascii(&snippet);

let expected_inner = Value::string_ascii_from_bytes(buffer.to_string().into_bytes())
.expect("buffer string should be valid ASCII");
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");

prop_assert_eq!(expected, evaluation);
}

#[test]
fn prop_to_ascii_from_ascii_utf8_strings(utf8_string in utf8_string_ascii_only_snippet_strategy()) {
let snippet = format!("(to-ascii? {utf8_string})");
let evaluation = evaluate_to_ascii(&snippet);

let ascii_snippet = &utf8_string[1..]; // Remove the u prefix
let expected_inner = execute_versioned(ascii_snippet, ClarityVersion::Clarity4)
.unwrap_or_else(|e| panic!("Execution failed for `{ascii_snippet}`: {e:?}"))
.unwrap_or_else(|| panic!("Execution returned no value for `{ascii_snippet}`"));
let expected = Value::okay(expected_inner).expect("response wrapping should succeed");

prop_assert_eq!(expected, evaluation);
}

#[test]
fn prop_to_ascii_from_utf8_strings(utf8_string in utf8_string_snippet_strategy()) {
let snippet = format!("(to-ascii? {utf8_string})");
let evaluation = evaluate_to_ascii(&snippet);

let literal_value = execute_versioned(&utf8_string, ClarityVersion::Clarity4)
.unwrap_or_else(|e| panic!("Execution failed for literal `{utf8_string}`: {e:?}"))
.unwrap_or_else(|| panic!("Execution returned no value for literal `{utf8_string}`"));

let utf8_chars = match &literal_value {
Value::Sequence(SequenceData::String(CharType::UTF8(data))) => data.data.clone(),
_ => panic!("Expected UTF-8 string literal, got `{literal_value:?}`"),
};
let is_ascii = utf8_chars
.iter()
.all(|char_bytes| char_bytes.len() == 1 && char_bytes[0].is_ascii());

if is_ascii {
let ascii_bytes: Vec<u8> = utf8_chars
.iter()
.map(|char_bytes| char_bytes[0])
.collect();
match Value::string_ascii_from_bytes(ascii_bytes) {
Ok(expected_inner) => {
let expected = Value::okay(expected_inner)
.expect("response wrapping should succeed");
prop_assert_eq!(expected, evaluation);
}
Err(_) => {
prop_assert_eq!(Value::err_uint(1), evaluation);
}
}
} else {
prop_assert_eq!(Value::err_uint(1), evaluation);
}
}
}
Loading
Loading