diff --git a/sip/sip-006-runtime-cost-assessment.md b/sip/sip-006-runtime-cost-assessment.md index f6b33d093..51d794178 100644 --- a/sip/sip-006-runtime-cost-assessment.md +++ b/sip/sip-006-runtime-cost-assessment.md @@ -485,7 +485,7 @@ X := the size of the list _entry_ type ### concat The cost of concatting two lists or buffers is linear in -the size of the two iterables: +the size of the two sequences: ``` a + b * (X+Y) diff --git a/sip/sip-008-analysis-cost-assessment.md b/sip/sip-008-analysis-cost-assessment.md index 9cec567e1..4cf34ddd7 100644 --- a/sip/sip-008-analysis-cost-assessment.md +++ b/sip/sip-008-analysis-cost-assessment.md @@ -222,9 +222,9 @@ checking pass. Some functions require additional work from the static analysis system. -## Functions on iterables (e.g., map, filter, fold) +## Functions on sequences (e.g., map, filter, fold) -Functions on iterables need to perform an additional check that the +Functions on sequences need to perform an additional check that the supplied type is a list or buffer before performing the normal argument type checking. This cost is assessed as: @@ -236,7 +236,7 @@ where a is a constant. ## Functions on options/responses -Similarly to the functions on iterables, option/response functions +Similarly to the functions on sequences, option/response functions must perform a simple check to see if the supplied input is an option or response before performing additional argument type checking. This cost is assessed as: diff --git a/src/vm/analysis/contract_interface_builder/mod.rs b/src/vm/analysis/contract_interface_builder/mod.rs index bc88cd135..e12f7324d 100644 --- a/src/vm/analysis/contract_interface_builder/mod.rs +++ b/src/vm/analysis/contract_interface_builder/mod.rs @@ -83,6 +83,8 @@ pub enum ContractInterfaceAtomType { bool, principal, buffer { length: u32 }, + string_utf8 { length: u32 }, + string_ascii { length: u32 }, tuple(Vec), optional(Box), response { ok: Box, error: Box }, @@ -125,6 +127,8 @@ impl ContractInterfaceAtomType { pub fn from_type_signature(sig: &TypeSignature) -> ContractInterfaceAtomType { use vm::types::TypeSignature::*; + use vm::types::{SequenceSubtype::*, StringSubtype::*}; + match sig { NoType => ContractInterfaceAtomType::none, IntType => ContractInterfaceAtomType::int128, @@ -132,9 +136,11 @@ impl ContractInterfaceAtomType { BoolType => ContractInterfaceAtomType::bool, PrincipalType => ContractInterfaceAtomType::principal, TraitReferenceType(_) => ContractInterfaceAtomType::trait_reference, - BufferType(len) => ContractInterfaceAtomType::buffer { length: len.into() }, - TupleType(sig) => Self::from_tuple_type(sig), - ListType(list_data) => { + TupleType(sig) => ContractInterfaceAtomType::from_tuple_type(sig), + SequenceType(StringType(ASCII(len))) => ContractInterfaceAtomType::string_ascii { length: len.into() }, + SequenceType(StringType(UTF8(len))) => ContractInterfaceAtomType::string_utf8 { length: len.into() }, + SequenceType(BufferType(len)) => ContractInterfaceAtomType::buffer { length: len.into() }, + SequenceType(ListType(list_data)) => { let (type_f, length) = list_data.clone().destruct(); ContractInterfaceAtomType::list { type_f: Box::new(Self::from_type_signature(&type_f)), length } diff --git a/src/vm/analysis/errors.rs b/src/vm/analysis/errors.rs index 99da6db37..34bd5ba7c 100644 --- a/src/vm/analysis/errors.rs +++ b/src/vm/analysis/errors.rs @@ -15,6 +15,7 @@ pub enum CheckErrors { MemoryBalanceExceeded(u64, u64), ValueTooLarge, + ValueOutOfBounds, TypeSignatureTooDeep, ExpectedName, @@ -111,7 +112,7 @@ pub enum CheckErrors { // expect a function, or applying a function to a list NonFunctionApplication, ExpectedListApplication, - ExpectedListOrBuffer(TypeSignature), + ExpectedSequence(TypeSignature), MaxLengthOverflow, // let syntax @@ -147,6 +148,9 @@ pub enum CheckErrors { TraitBasedContractCallInReadOnly, ContractOfExpectsTrait, + // strings + InvalidCharactersDetected, + WriteAttemptedInReadOnly, AtBlockClosureMustBeReadOnly } @@ -287,6 +291,7 @@ impl DiagnosableError for CheckErrors { CheckErrors::BadSyntaxExpectedListOfPairs => "bad syntax: function expects a list of pairs to bind names, e.g., ((name-0 a) (name-1 b) ...)".into(), CheckErrors::UnknownTypeName(name) => format!("failed to parse type: '{}'", name), CheckErrors::ValueTooLarge => format!("created a type which was greater than maximum allowed value size"), + CheckErrors::ValueOutOfBounds => format!("created a type which value size was out of defined bounds"), CheckErrors::TypeSignatureTooDeep => "created a type which was deeper than maximum allowed type depth".into(), CheckErrors::ExpectedName => format!("expected a name argument to this function"), CheckErrors::NoSuperType(a, b) => format!("unable to create a supertype for the two types: '{}' and '{}'", a, b), @@ -335,7 +340,7 @@ impl DiagnosableError for CheckErrors { CheckErrors::NameAlreadyUsed(name) => format!("defining '{}' conflicts with previous value", name), CheckErrors::NonFunctionApplication => format!("expecting expression of type function"), CheckErrors::ExpectedListApplication => format!("expecting expression of type list"), - CheckErrors::ExpectedListOrBuffer(found_type) => format!("expecting expression of type 'list' or 'buff', found '{}'", found_type), + CheckErrors::ExpectedSequence(found_type) => format!("expecting expression of type 'list', 'buff', 'string-ascii' or 'string-utf8' - found '{}'", found_type), CheckErrors::MaxLengthOverflow => format!("expecting a value <= {}", u32::max_value()), CheckErrors::BadLetSyntax => format!("invalid syntax of 'let'"), CheckErrors::CircularReference(function_names) => format!("detected interdependent functions ({})", function_names.join(", ")), @@ -368,6 +373,7 @@ impl DiagnosableError for CheckErrors { CheckErrors::DefineTraitBadSignature => format!("invalid trait definition"), CheckErrors::TraitReferenceNotAllowed => format!("trait references can not be stored"), CheckErrors::ContractOfExpectsTrait => format!("trait reference expected"), + CheckErrors::InvalidCharactersDetected => format!("invalid characters detected"), CheckErrors::TypeAlreadyAnnotatedFailure | CheckErrors::CheckerImplementationFailure => { format!("internal error - please file an issue on github.com/blockstack/blockstack-core") }, diff --git a/src/vm/analysis/tests/mod.rs b/src/vm/analysis/tests/mod.rs index 658f1e1ee..3ab5fd70a 100644 --- a/src/vm/analysis/tests/mod.rs +++ b/src/vm/analysis/tests/mod.rs @@ -208,7 +208,7 @@ fn test_non_function_application() { fn test_expected_list_or_buff() { let snippet = "(filter not 4)"; let err = mem_type_check(snippet).unwrap_err(); - assert!(format!("{}", err.diagnostic).contains("expecting expression of type 'list' or 'buff'")); + assert!(format!("{}", err.diagnostic).contains("expecting expression of type 'list', 'buff', 'string-ascii' or 'string-utf8'")); } #[test] diff --git a/src/vm/analysis/type_checker/natives/mod.rs b/src/vm/analysis/type_checker/natives/mod.rs index cfdf7b633..4b107596c 100644 --- a/src/vm/analysis/type_checker/natives/mod.rs +++ b/src/vm/analysis/type_checker/natives/mod.rs @@ -12,7 +12,7 @@ use std::convert::TryFrom; use vm::costs::{cost_functions, analysis_typecheck_cost, CostOverflowingMath}; mod assets; -mod iterables; +mod sequences; mod maps; mod options; @@ -415,13 +415,13 @@ impl TypedNativeFunction { Let => Special(SpecialNativeFunction(&check_special_let)), FetchVar => Special(SpecialNativeFunction(&check_special_fetch_var)), SetVar => Special(SpecialNativeFunction(&check_special_set_var)), - Map => Special(SpecialNativeFunction(&iterables::check_special_map)), - Filter => Special(SpecialNativeFunction(&iterables::check_special_filter)), - Fold => Special(SpecialNativeFunction(&iterables::check_special_fold)), - Append => Special(SpecialNativeFunction(&iterables::check_special_append)), - Concat => Special(SpecialNativeFunction(&iterables::check_special_concat)), - AsMaxLen => Special(SpecialNativeFunction(&iterables::check_special_as_max_len)), - Len => Special(SpecialNativeFunction(&iterables::check_special_len)), + Map => Special(SpecialNativeFunction(&sequences::check_special_map)), + Filter => Special(SpecialNativeFunction(&sequences::check_special_filter)), + Fold => Special(SpecialNativeFunction(&sequences::check_special_fold)), + Append => Special(SpecialNativeFunction(&sequences::check_special_append)), + Concat => Special(SpecialNativeFunction(&sequences::check_special_concat)), + AsMaxLen => Special(SpecialNativeFunction(&sequences::check_special_as_max_len)), + Len => Special(SpecialNativeFunction(&sequences::check_special_len)), ListCons => Special(SpecialNativeFunction(&check_special_list_cons)), FetchEntry => Special(SpecialNativeFunction(&maps::check_special_fetch_entry)), SetEntry => Special(SpecialNativeFunction(&maps::check_special_set_entry)), diff --git a/src/vm/analysis/type_checker/natives/iterables.rs b/src/vm/analysis/type_checker/natives/sequences.rs similarity index 60% rename from src/vm/analysis/type_checker/natives/iterables.rs rename to src/vm/analysis/type_checker/natives/sequences.rs index 808992948..689caf61e 100644 --- a/src/vm/analysis/type_checker/natives/iterables.rs +++ b/src/vm/analysis/type_checker/natives/sequences.rs @@ -1,8 +1,9 @@ use vm::functions::NativeFunctions; use vm::representations::{SymbolicExpression, SymbolicExpressionType}; -use vm::types::{ TypeSignature, FunctionType }; +use vm::types::{TypeSignature, FunctionType}; +use vm::types::{SequenceSubtype::*, StringSubtype::*}; use vm::types::{Value, MAX_VALUE_SIZE}; -pub use vm::types::signatures::{ListTypeData, BufferLength}; +pub use vm::types::signatures::{ListTypeData, BufferLength, StringUTF8Length}; use std::convert::TryFrom; use std::convert::TryInto; @@ -38,20 +39,28 @@ pub fn check_special_map(checker: &mut TypeChecker, args: &[SymbolicExpression], runtime_cost!(cost_functions::ANALYSIS_ITERABLE_FUNC, checker, 1)?; let argument_type = checker.type_check(&args[1], context)?; - match argument_type { - TypeSignature::ListType(list_data) => { + let (mapped_type, len) = match argument_type { + TypeSignature::SequenceType(ListType(list_data)) => { let (arg_items_type, arg_length) = list_data.destruct(); let mapped_type = function_type.check_args(checker, &[arg_items_type])?; - TypeSignature::list_of(mapped_type, arg_length) - .map_err(|_| CheckErrors::ConstructedListTooLarge.into()) + (mapped_type, arg_length) }, - TypeSignature::BufferType(buffer_data) => { + TypeSignature::SequenceType(BufferType(buffer_data)) => { let mapped_type = function_type.check_args(checker, &[TypeSignature::min_buffer()])?; - TypeSignature::list_of(mapped_type, buffer_data.into()) - .map_err(|_| CheckErrors::ConstructedListTooLarge.into()) + (mapped_type, buffer_data.into()) }, - _ => Err(CheckErrors::ExpectedListOrBuffer(argument_type).into()) - } + TypeSignature::SequenceType(StringType(ASCII(ascii_data))) => { + let mapped_type = function_type.check_args(checker, &[TypeSignature::min_string_ascii()])?; + (mapped_type, ascii_data.into()) + }, + TypeSignature::SequenceType(StringType(UTF8(utf8_data))) => { + let mapped_type = function_type.check_args(checker, &[TypeSignature::min_string_utf8()])?; + (mapped_type, utf8_data.into()) + }, + _ => return Err(CheckErrors::ExpectedSequence(argument_type).into()) + }; + TypeSignature::list_of(mapped_type, len) + .map_err(|_| CheckErrors::ConstructedListTooLarge.into()) } pub fn check_special_filter(checker: &mut TypeChecker, args: &[SymbolicExpression], context: &TypingContext) -> TypeResult { @@ -68,9 +77,8 @@ pub fn check_special_filter(checker: &mut TypeChecker, args: &[SymbolicExpressio { let input_type = match argument_type { - TypeSignature::ListType(ref list_data) => Ok(list_data.clone().destruct().0), - TypeSignature::BufferType(_) => Ok(TypeSignature::min_buffer()), - _ => Err(CheckErrors::ExpectedListOrBuffer(argument_type.clone())) + TypeSignature::SequenceType(ref sequence_type) => Ok(sequence_type.unit_type()), + _ => Err(CheckErrors::ExpectedSequence(argument_type.clone())) }?; let filter_type = function_type.check_args(checker, &[input_type])?; @@ -96,9 +104,8 @@ pub fn check_special_fold(checker: &mut TypeChecker, args: &[SymbolicExpression] let argument_type = checker.type_check(&args[1], context)?; let input_type = match argument_type { - TypeSignature::ListType(list_data) => Ok(list_data.destruct().0), - TypeSignature::BufferType(_) => Ok(TypeSignature::min_buffer()), - _ => Err(CheckErrors::ExpectedListOrBuffer(argument_type)) + TypeSignature::SequenceType(sequence_type) => Ok(sequence_type.unit_type()), + _ => Err(CheckErrors::ExpectedSequence(argument_type)) }?; let initial_value_type = checker.type_check(&args[2], context)?; @@ -126,33 +133,40 @@ pub fn check_special_concat(checker: &mut TypeChecker, args: &[SymbolicExpressio analysis_typecheck_cost(checker, &lhs_type, &rhs_type)?; - match lhs_type { - TypeSignature::ListType(lhs_list) => { - if let TypeSignature::ListType(rhs_list) = rhs_type { - let (lhs_entry_type, lhs_max_len) = lhs_list.destruct(); - let (rhs_entry_type, rhs_max_len) = rhs_list.destruct(); - - let list_entry_type = TypeSignature::least_supertype(&lhs_entry_type, &rhs_entry_type)?; - let new_len = lhs_max_len.checked_add(rhs_max_len) - .ok_or(CheckErrors::MaxLengthOverflow)?; - let return_type = TypeSignature::list_of(list_entry_type, new_len)?; - return Ok(return_type); - } else { - return Err(CheckErrors::TypeError(rhs_type.clone(), TypeSignature::ListType(lhs_list)).into()); + let res = match (&lhs_type, &rhs_type) { + (TypeSignature::SequenceType(lhs_seq), TypeSignature::SequenceType(rhs_seq)) => { + match (lhs_seq, rhs_seq) { + (ListType(lhs_list), ListType(rhs_list)) => { + let (lhs_entry_type, lhs_max_len) = (lhs_list.get_list_item_type(), lhs_list.get_max_len()); + let (rhs_entry_type, rhs_max_len) = (rhs_list.get_list_item_type(), rhs_list.get_max_len()); + + let list_entry_type = TypeSignature::least_supertype(lhs_entry_type, rhs_entry_type)?; + let new_len = lhs_max_len.checked_add(rhs_max_len) + .ok_or(CheckErrors::MaxLengthOverflow)?; + let type_sig = TypeSignature::list_of(list_entry_type, new_len)?; + type_sig + }, + (BufferType(lhs_len), BufferType(rhs_len)) => { + let size: u32 = u32::from(lhs_len).checked_add(u32::from(rhs_len)) + .ok_or(CheckErrors::MaxLengthOverflow)?; + TypeSignature::SequenceType(BufferType(size.try_into()?)) + }, + (StringType(ASCII(lhs_len)), StringType(ASCII(rhs_len))) => { + let size: u32 = u32::from(lhs_len).checked_add(u32::from(rhs_len)) + .ok_or(CheckErrors::MaxLengthOverflow)?; + TypeSignature::SequenceType(StringType(ASCII(size.try_into()?))) + }, + (StringType(UTF8(lhs_len)), StringType(UTF8(rhs_len))) => { + let size: u32 = u32::from(lhs_len).checked_add(u32::from(rhs_len)) + .ok_or(CheckErrors::MaxLengthOverflow)?; + TypeSignature::SequenceType(StringType(UTF8(size.try_into()?))) + }, + (_, _) => return Err(CheckErrors::TypeError(lhs_type.clone(), rhs_type.clone()).into()) } - }, - TypeSignature::BufferType(lhs_buff_len) => { - if let TypeSignature::BufferType(rhs_buff_len) = rhs_type { - let size: u32 = u32::from(lhs_buff_len).checked_add(u32::from(rhs_buff_len)) - .ok_or(CheckErrors::MaxLengthOverflow)?; - let return_type = TypeSignature::BufferType(size.try_into()?); - return Ok(return_type); - } else { - return Err(CheckErrors::TypeError(rhs_type.clone(), TypeSignature::max_buffer()).into()); - } - }, - _ => Err(CheckErrors::ExpectedListOrBuffer(lhs_type.clone()).into()) - } + } + _ => return Err(CheckErrors::ExpectedSequence(lhs_type.clone()).into()) + }; + Ok(res) } pub fn check_special_append(checker: &mut TypeChecker, args: &[SymbolicExpression], context: &TypingContext) -> TypeResult { @@ -162,7 +176,7 @@ pub fn check_special_append(checker: &mut TypeChecker, args: &[SymbolicExpressio let lhs_type = checker.type_check(&args[0], context)?; match lhs_type { - TypeSignature::ListType(lhs_list) => { + TypeSignature::SequenceType(ListType(lhs_list)) => { let rhs_type = checker.type_check(&args[1], context)?; let (lhs_entry_type, lhs_max_len) = lhs_list.destruct(); @@ -194,19 +208,25 @@ pub fn check_special_as_max_len(checker: &mut TypeChecker, args: &[SymbolicExpre let expected_len = u32::try_from(expected_len) .map_err(|_e| CheckErrors::MaxLengthOverflow)?; - let iterable = checker.type_check(&args[0], context)?; - analysis_typecheck_cost(checker, &iterable, &iterable)?; + let sequence = checker.type_check(&args[0], context)?; + analysis_typecheck_cost(checker, &sequence, &sequence)?; - match iterable { - TypeSignature::ListType(list) => { + match sequence { + TypeSignature::SequenceType(ListType(list)) => { let (lhs_entry_type, _) = list.destruct(); let resized_list = ListTypeData::new_list(lhs_entry_type, expected_len)?; - Ok(TypeSignature::OptionalType(Box::new(TypeSignature::ListType(resized_list)))) + Ok(TypeSignature::OptionalType(Box::new(TypeSignature::SequenceType(ListType(resized_list))))) }, - TypeSignature::BufferType(_) => { - Ok(TypeSignature::OptionalType(Box::new(TypeSignature::BufferType(BufferLength::try_from(expected_len).unwrap())))) + TypeSignature::SequenceType(BufferType(_)) => { + Ok(TypeSignature::OptionalType(Box::new(TypeSignature::SequenceType(BufferType(BufferLength::try_from(expected_len).unwrap()))))) }, - _ => Err(CheckErrors::ExpectedListOrBuffer(iterable).into()) + TypeSignature::SequenceType(StringType(ASCII(_))) => { + Ok(TypeSignature::OptionalType(Box::new(TypeSignature::SequenceType(StringType(ASCII(BufferLength::try_from(expected_len).unwrap())))))) + }, + TypeSignature::SequenceType(StringType(UTF8(_))) => { + Ok(TypeSignature::OptionalType(Box::new(TypeSignature::SequenceType(StringType(UTF8(StringUTF8Length::try_from(expected_len).unwrap())))))) + }, + _ => Err(CheckErrors::ExpectedSequence(sequence).into()) } } @@ -217,8 +237,8 @@ pub fn check_special_len(checker: &mut TypeChecker, args: &[SymbolicExpression], runtime_cost!(cost_functions::ANALYSIS_ITERABLE_FUNC, checker, 1)?; match collection_type { - TypeSignature::ListType(_) | TypeSignature::BufferType(_) => Ok(()), - _ => Err(CheckErrors::ExpectedListOrBuffer(collection_type.clone())) + TypeSignature::SequenceType(_) => Ok(()), + _ => Err(CheckErrors::ExpectedSequence(collection_type.clone())) }?; Ok(TypeSignature::UIntType) diff --git a/src/vm/analysis/type_checker/tests/assets.rs b/src/vm/analysis/type_checker/tests/assets.rs index fe39df0b1..59ace2671 100644 --- a/src/vm/analysis/type_checker/tests/assets.rs +++ b/src/vm/analysis/type_checker/tests/assets.rs @@ -1,16 +1,16 @@ use vm::database::MemoryBackingStore; -use vm::types::{TypeSignature, QualifiedContractIdentifier}; +use vm::types::{TypeSignature, SequenceSubtype, StringSubtype, QualifiedContractIdentifier}; use vm::ast::parse; use vm::analysis::errors::CheckErrors; use vm::analysis::{AnalysisDatabase, mem_type_check}; use std::convert::TryInto; -fn buff_type(size: u32) -> TypeSignature { - TypeSignature::BufferType(size.try_into().unwrap()).into() +fn string_ascii_type(size: u32) -> TypeSignature { + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(size.try_into().unwrap()))).into() } const FIRST_CLASS_TOKENS: &str = "(define-fungible-token stackaroos) - (define-non-fungible-token stacka-nfts (buff 10)) + (define-non-fungible-token stacka-nfts (string-ascii 10)) (nft-get-owner? stacka-nfts \"1234567890\" ) (define-read-only (my-ft-get-balance (account principal)) (ft-get-balance stackaroos account)) @@ -142,16 +142,16 @@ fn test_bad_asset_usage() { TypeSignature::IntType), CheckErrors::BadTokenName, CheckErrors::NoSuchNFT("stackoos".to_string()), - CheckErrors::TypeError(buff_type(10), + CheckErrors::TypeError(string_ascii_type(10), TypeSignature::UIntType), - CheckErrors::TypeError(buff_type(10), - buff_type(15)), + CheckErrors::TypeError(string_ascii_type(10), + string_ascii_type(15)), CheckErrors::BadTokenName, CheckErrors::NoSuchNFT("stackoos".to_string()), - CheckErrors::TypeError(buff_type(10), + CheckErrors::TypeError(string_ascii_type(10), TypeSignature::UIntType), - CheckErrors::TypeError(buff_type(10), - buff_type(15)), + CheckErrors::TypeError(string_ascii_type(10), + string_ascii_type(15)), CheckErrors::TypeError(TypeSignature::PrincipalType, TypeSignature::UIntType), CheckErrors::NoSuchFT("stackoos".to_string()), @@ -166,7 +166,7 @@ fn test_bad_asset_usage() { TypeSignature::UIntType), CheckErrors::TypeError(TypeSignature::PrincipalType, TypeSignature::UIntType), - CheckErrors::TypeError(buff_type(10), + CheckErrors::TypeError(string_ascii_type(10), TypeSignature::UIntType), CheckErrors::NoSuchFT("stackoos".to_string()), CheckErrors::BadTokenName, diff --git a/src/vm/analysis/type_checker/tests/mod.rs b/src/vm/analysis/type_checker/tests/mod.rs index 3562ffb31..6a759f6a8 100644 --- a/src/vm/analysis/type_checker/tests/mod.rs +++ b/src/vm/analysis/type_checker/tests/mod.rs @@ -12,7 +12,9 @@ use vm::types::{Value, PrincipalData, TypeSignature, FunctionType, FixedFunction QualifiedContractIdentifier}; use vm::database::MemoryBackingStore; -use vm::types::TypeSignature::{IntType, BoolType, BufferType, UIntType, PrincipalType}; +use vm::types::TypeSignature::{IntType, BoolType, SequenceType, UIntType, PrincipalType}; +use vm::types::{SequenceSubtype::*, StringSubtype::*}; + use std::convert::TryInto; mod assets; @@ -23,7 +25,11 @@ fn type_check_helper(exp: &str) -> TypeResult { } fn buff_type(size: u32) -> TypeSignature { - TypeSignature::BufferType(size.try_into().unwrap()).into() + TypeSignature::SequenceType(BufferType(size.try_into().unwrap())).into() +} + +fn ascii_type(size: u32) -> TypeSignature { + TypeSignature::SequenceType(StringType(ASCII(size.try_into().unwrap()))).into() } #[test] @@ -410,7 +416,7 @@ fn test_simple_ifs() { "(if true true false)", "(if true \"abcdef\" \"abc\")", "(if true \"a\" \"abcdef\")" ]; - let expected = [ "int", "bool", "(buff 6)", "(buff 6)" ]; + let expected = [ "int", "bool", "(string-ascii 6)", "(string-ascii 6)" ]; let bad = ["(if true true 1)", "(if true \"a\" false)", @@ -419,7 +425,7 @@ fn test_simple_ifs() { let bad_expected = [ CheckErrors::IfArmsMustMatch(BoolType, IntType), - CheckErrors::IfArmsMustMatch(buff_type(1), BoolType), + CheckErrors::IfArmsMustMatch(ascii_type(1), BoolType), CheckErrors::IncorrectArgumentCount(3, 0), CheckErrors::TypeError(BoolType, IntType) ]; @@ -563,8 +569,8 @@ fn test_lists() { CheckErrors::IllegalOrUnknownFunctionApplication("if".to_string()), CheckErrors::IncorrectArgumentCount(2, 1), CheckErrors::UnionTypeError(vec![IntType, UIntType], BoolType), - CheckErrors::ExpectedListOrBuffer(UIntType), - CheckErrors::ExpectedListOrBuffer(IntType)]; + CheckErrors::ExpectedSequence(UIntType), + CheckErrors::ExpectedSequence(IntType)]; for (good_test, expected) in good.iter().zip(expected.iter()) { assert_eq!(expected, &format!("{}", type_check_helper(&good_test).unwrap())); @@ -583,8 +589,8 @@ fn test_buff() { "(if true \"block\" \"blockstack\")", "(len \"blockstack\")"]; let expected = [ - "(buff 10)", - "(buff 10)", + "(string-ascii 10)", + "(string-ascii 10)", "uint"]; let bad = [ "(fold and (list true false) 2)", @@ -614,8 +620,8 @@ fn test_buff() { CheckErrors::IllegalOrUnknownFunctionApplication("if".to_string()), CheckErrors::IncorrectArgumentCount(2, 1), CheckErrors::UnionTypeError(vec![IntType, UIntType], BoolType), - CheckErrors::ExpectedListOrBuffer(UIntType), - CheckErrors::ExpectedListOrBuffer(IntType)]; + CheckErrors::ExpectedSequence(UIntType), + CheckErrors::ExpectedSequence(IntType)]; for (good_test, expected) in good.iter().zip(expected.iter()) { assert_eq!(expected, &format!("{}", type_check_helper(&good_test).unwrap())); @@ -630,13 +636,13 @@ fn test_buff() { fn test_buff_fold() { let good = [ "(define-private (get-len (x (buff 1)) (acc uint)) (+ acc u1)) - (fold get-len \"101010\" u0)", + (fold get-len 0x000102030405 u0)", "(define-private (slice (x (buff 1)) (acc (tuple (limit uint) (cursor uint) (data (buff 10))))) (if (< (get cursor acc) (get limit acc)) (let ((data (default-to (get data acc) (as-max-len? (concat (get data acc) x) u10)))) (tuple (limit (get limit acc)) (cursor (+ u1 (get cursor acc))) (data data))) acc)) - (fold slice \"0123456789\" (tuple (limit u5) (cursor u0) (data \"\")))"]; + (fold slice 0x00010203040506070809 (tuple (limit u5) (cursor u0) (data 0x)))"]; let expected = ["uint", "(tuple (cursor uint) (data (buff 10)) (limit uint))"]; for (good_test, expected) in good.iter().zip(expected.iter()) { @@ -648,7 +654,7 @@ fn test_buff_fold() { #[test] fn test_buff_map() { let good = [ - "(map hash160 \"12345\")"]; + "(map hash160 0x0102030405)"]; let expected = ["(list 5 (buff 20))"]; for (good_test, expected) in good.iter().zip(expected.iter()) { @@ -674,9 +680,9 @@ fn test_buff_as_max_len() { "(as-max-len? \"12345\" u8)", "(as-max-len? \"12345\" u4)"]; let expected = [ - "(optional (buff 5))", - "(optional (buff 8))", - "(optional (buff 4))"]; + "(optional (string-ascii 5))", + "(optional (string-ascii 8))", + "(optional (string-ascii 4))"]; for (test, expected) in tests.iter().zip(expected.iter()) { assert_eq!(expected, &format!("{}", type_check_helper(&test).unwrap())); @@ -756,7 +762,7 @@ fn test_concat_append_supertypes() { #[test] fn test_buff_concat() { let good = [ - "(concat \"123\" \"58\")"]; + "(concat 0x010203 0x0405)"]; let expected = ["(buff 5)"]; for (good_test, expected) in good.iter().zip(expected.iter()) { @@ -767,9 +773,9 @@ fn test_buff_concat() { #[test] fn test_buff_filter() { let good = [ - "(define-private (f (e (buff 1))) (is-eq e \"1\")) + "(define-private (f (e (string-ascii 1))) (is-eq e \"1\")) (filter f \"101010\")"]; - let expected = ["(buff 6)"]; + let expected = ["(string-ascii 6)"]; for (good_test, expected) in good.iter().zip(expected.iter()) { let type_sig = mem_type_check(good_test).unwrap().0.unwrap(); @@ -1162,10 +1168,10 @@ fn test_set_list_variable() { #[test] fn test_set_buffer_variable() { let contract_src = r#" - (define-data-var name (buff 5) "alice") + (define-data-var name (string-ascii 5) "alice") (define-private (get-name) (var-get name)) - (define-private (set-name (new-name (buff 3))) + (define-private (set-name (new-name (string-ascii 3))) (if (var-set name new-name) new-name (get-name))) @@ -1357,8 +1363,8 @@ fn test_tuple_map() { (get name (get contents (map-get? tuples (tuple (name name)))))) - (add-tuple 0 \"abcde\") - (add-tuple 1 \"abcd\") + (add-tuple 0 0x0102030405) + (add-tuple 1 0x01020304) (list (get-tuple 0) (get-tuple 1)) "; @@ -1699,3 +1705,135 @@ fn test_set_entry_unbound_variables() { }); } } + +#[test] +fn test_string_ascii_fold() { + let good = [ + "(define-private (get-len (x (string-ascii 1)) (acc uint)) (+ acc u1)) + (fold get-len \"blockstack\" u0)", + "(define-private (slice (x (string-ascii 1)) (acc (tuple (limit uint) (cursor uint) (data (string-ascii 10))))) + (if (< (get cursor acc) (get limit acc)) + (let ((data (default-to (get data acc) (as-max-len? (concat (get data acc) x) u10)))) + (tuple (limit (get limit acc)) (cursor (+ u1 (get cursor acc))) (data data))) + acc)) + (fold slice \"blockstack\" (tuple (limit u5) (cursor u0) (data \"\")))"]; + let expected = ["uint", "(tuple (cursor uint) (data (string-ascii 10)) (limit uint))"]; + + for (good_test, expected) in good.iter().zip(expected.iter()) { + let type_sig = mem_type_check(good_test).unwrap().0.unwrap(); + assert_eq!(expected, &type_sig.to_string()); + } +} + +#[test] +fn test_string_ascii_as_max_len() { + let tests = [ + "(as-max-len? \"12345\" u5)", + "(as-max-len? \"12345\" u8)", + "(as-max-len? \"12345\" u4)"]; + let expected = [ + "(optional (string-ascii 5))", + "(optional (string-ascii 8))", + "(optional (string-ascii 4))"]; + + for (test, expected) in tests.iter().zip(expected.iter()) { + assert_eq!(expected, &format!("{}", type_check_helper(&test).unwrap())); + } +} + +#[test] +fn test_string_ascii_concat() { + let good = [ + "(concat \"block\" \"stack\")"]; + let expected = ["(string-ascii 10)"]; + + for (good_test, expected) in good.iter().zip(expected.iter()) { + assert_eq!(expected, &format!("{}", type_check_helper(&good_test).unwrap())); + } +} + + +#[test] +fn test_string_utf8_fold() { + let good = [ + "(define-private (get-len (x (string-utf8 1)) (acc uint)) (+ acc u1)) + (fold get-len u\"blockstack\" u0)", + "(define-private (slice (x (string-utf8 1)) (acc (tuple (limit uint) (cursor uint) (data (string-utf8 11))))) + (if (< (get cursor acc) (get limit acc)) + (let ((data (default-to (get data acc) (as-max-len? (concat (get data acc) x) u11)))) + (tuple (limit (get limit acc)) (cursor (+ u1 (get cursor acc))) (data data))) + acc)) + (fold slice u\"blockstack\\u{1F926}\" (tuple (limit u5) (cursor u0) (data u\"\")))"]; + let expected = ["uint", "(tuple (cursor uint) (data (string-utf8 11)) (limit uint))"]; + + for (good_test, expected) in good.iter().zip(expected.iter()) { + let type_sig = mem_type_check(good_test).unwrap().0.unwrap(); + assert_eq!(expected, &type_sig.to_string()); + } +} + +#[test] +fn test_string_utf8_as_max_len() { + let tests = [ + "(as-max-len? u\"1234\\u{1F926}\" u5)", + "(as-max-len? u\"1234\\u{1F926}\" u8)", + "(as-max-len? u\"1234\\u{1F926}\" u4)"]; + let expected = [ + "(optional (string-utf8 5))", + "(optional (string-utf8 8))", + "(optional (string-utf8 4))"]; + + for (test, expected) in tests.iter().zip(expected.iter()) { + assert_eq!(expected, &format!("{}", type_check_helper(&test).unwrap())); + } +} + +#[test] +fn test_string_utf8_concat() { + let good = [ + "(concat u\"block\" u\"stack\\u{1F926}\")"]; + let expected = ["(string-utf8 11)"]; + + for (good_test, expected) in good.iter().zip(expected.iter()) { + assert_eq!(expected, &format!("{}", type_check_helper(&good_test).unwrap())); + } +} + +#[test] +fn test_buff_negative_len() { + let contract_src = + "(define-private (func (x (buff -12))) (len x)) + (func 0x00)"; + + let res = mem_type_check(&contract_src).unwrap_err(); + assert!(match &res.err { + &CheckErrors::BadSyntaxBinding => true, + _ => false + }); +} + +#[test] +fn test_string_ascii_negative_len() { + let contract_src = + "(define-private (func (x (string-ascii -12))) (len x)) + (func \"\")"; + + let res = mem_type_check(&contract_src).unwrap_err(); + assert!(match &res.err { + &CheckErrors::BadSyntaxBinding => true, + _ => false + }); +} + +#[test] +fn test_string_utf8_negative_len() { + let contract_src = + "(define-private (func (x (string-utf8 -12))) (len x)) + (func u\"\")"; + + let res = mem_type_check(&contract_src).unwrap_err(); + assert!(match &res.err { + &CheckErrors::BadSyntaxBinding => true, + _ => false + }); +} diff --git a/src/vm/ast/errors.rs b/src/vm/ast/errors.rs index c1342f843..36b7403e6 100644 --- a/src/vm/ast/errors.rs +++ b/src/vm/ast/errors.rs @@ -44,6 +44,8 @@ pub enum ParseErrors { TraitReferenceUnknown(String), CommaSeparatorUnexpected, ColonSeparatorUnexpected, + InvalidCharactersDetected, + InvalidEscaping, } #[derive(Debug, PartialEq)] @@ -155,6 +157,8 @@ impl DiagnosableError for ParseErrors { ParseErrors::TraitReferenceNotAllowed => format!("trait references can not be stored"), ParseErrors::TraitReferenceUnknown(trait_name) => format!("use of undeclared trait <{}>", trait_name), ParseErrors::ExpressionStackDepthTooDeep => format!("AST has too deep of an expression nesting. The maximum stack depth is {}", MAX_CALL_STACK_DEPTH), + ParseErrors::InvalidCharactersDetected => format!("invalid characters detected"), + ParseErrors::InvalidEscaping => format!("invalid escaping detected in string"), } } diff --git a/src/vm/ast/parser/mod.rs b/src/vm/ast/parser/mod.rs index 57698e9dc..b7943100d 100644 --- a/src/vm/ast/parser/mod.rs +++ b/src/vm/ast/parser/mod.rs @@ -32,7 +32,7 @@ enum TokenType { Whitespace, Comma, Colon, LParens, RParens, LCurly, RCurly, - StringLiteral, HexStringLiteral, + StringASCIILiteral, StringUTF8Literal, HexStringLiteral, UIntLiteral, IntLiteral, Variable, TraitReferenceLiteral, PrincipalLiteral, SugaredContractIdentifierLiteral, @@ -96,7 +96,8 @@ pub fn lex(input: &str) -> ParseResult> { // it's worth either (1) an extern macro, or (2) the complexity of hand implementing. let lex_matchers: &[LexMatcher] = &[ - LexMatcher::new(r##""(?P((\\")|([[ -~]&&[^"]]))*)""##, TokenType::StringLiteral), + LexMatcher::new(r##"u"(?P((\\")|([[ -~]&&[^"]]))*)""##, TokenType::StringUTF8Literal), + LexMatcher::new(r##""(?P((\\")|([[ -~]&&[^"]]))*)""##, TokenType::StringASCIILiteral), LexMatcher::new(";;[ -~]*", TokenType::Whitespace), // ;; comments. LexMatcher::new("[\n]+", TokenType::Whitespace), LexMatcher::new("[ \t]+", TokenType::Whitespace), @@ -107,7 +108,7 @@ pub fn lex(input: &str) -> ParseResult> { LexMatcher::new("[{]", TokenType::LCurly), LexMatcher::new("[}]", TokenType::RCurly), LexMatcher::new("<(?P([[:word:]]|[-])+)>", TokenType::TraitReferenceLiteral), - LexMatcher::new("0x(?P[[:xdigit:]]+)", TokenType::HexStringLiteral), + LexMatcher::new("0x(?P[[:xdigit:]]*)", TokenType::HexStringLiteral), LexMatcher::new("u(?P[[:digit:]]+)", TokenType::UIntLiteral), LexMatcher::new("(?P-?[[:digit:]]+)", TokenType::IntLiteral), LexMatcher::new(&format!(r#"'(?P{}(\.)([[:alnum:]]|[-]){{1,{}}})"#, @@ -290,17 +291,32 @@ pub fn lex(input: &str) -> ParseResult> { }?; Ok(LexItem::LiteralValue(str_value.len(), value)) }, - TokenType::StringLiteral => { + TokenType::StringASCIILiteral => { let str_value = get_value_or_err(current_slice, captures)?; - let quote_unescaped = str_value.replace("\\\"","\""); - let slash_unescaped = quote_unescaped.replace("\\\\","\\"); - let byte_vec = slash_unescaped.as_bytes().to_vec(); - let value = match Value::buff_from(byte_vec) { + let str_value_len = str_value.len(); + let unescaped_str = unescape_ascii_chars(str_value, false)?; + let byte_vec = unescaped_str + .as_bytes() + .to_vec(); + + let value = match Value::string_ascii_from_bytes(byte_vec) { Ok(parsed) => Ok(parsed), - Err(_e) => Err(ParseError::new(ParseErrors::FailedParsingBuffer(str_value.clone()))) + Err(_e) => Err(ParseError::new(ParseErrors::InvalidCharactersDetected)) }?; - Ok(LexItem::LiteralValue(str_value.len(), value)) + Ok(LexItem::LiteralValue(str_value_len, value)) }, + TokenType::StringUTF8Literal => { + let str_value = get_value_or_err(current_slice, captures)?; + let str_value_len = str_value.len(); + let unescaped_str = unescape_ascii_chars(str_value, true)?; + + let value = match Value::string_utf8_from_string_utf8_literal(unescaped_str) { + Ok(parsed) => Ok(parsed), + Err(_e) => Err(ParseError::new(ParseErrors::InvalidCharactersDetected)) + }?; + Ok(LexItem::LiteralValue(str_value_len, value)) + }, + }?; result.push((token, current_line, column_pos)); @@ -318,6 +334,34 @@ pub fn lex(input: &str) -> ParseResult> { } } +fn unescape_ascii_chars(escaped_str: String, allow_unicode_escape: bool) -> ParseResult { + let mut unescaped_str = String::new(); + let mut chars = escaped_str.chars().into_iter(); + while let Some(char) = chars.next() { + if char == '\\' { + if let Some(next) = chars.next() { + match next { + // ASCII escapes based on Rust list (https://doc.rust-lang.org/reference/tokens.html#ascii-escapes) + '\\' => unescaped_str.push('\\'), + '\"' => unescaped_str.push('\"'), + 'n' => unescaped_str.push('\n'), + 't' => unescaped_str.push('\t'), + 'r' => unescaped_str.push('\r'), + '0' => unescaped_str.push('\0'), + 'u' if allow_unicode_escape == true => + unescaped_str.push_str("\\u"), + _ => return Err(ParseError::new(ParseErrors::InvalidEscaping)) + } + } else { + return Err(ParseError::new(ParseErrors::InvalidEscaping)) + } + } else { + unescaped_str.push(char); + } + } + Ok(unescaped_str) +} + enum ParseStackItem { Expression(PreSymbolicExpression), Colon, Comma @@ -516,8 +560,8 @@ pub fn parse(input: &str) -> ParseResult> { #[cfg(test)] mod test { use vm::representations::{PreSymbolicExpression, PreSymbolicExpressionType}; - use vm::{Value, ast}; - use vm::types::{QualifiedContractIdentifier, PrincipalData}; + use vm::ast; + use vm::types::{QualifiedContractIdentifier, PrincipalData, Value, CharType, SequenceData}; use vm::ast::errors::{ParseErrors, ParseError}; use vm::types::{TraitIdentifier}; @@ -699,8 +743,22 @@ r#"z (let ((x 1) (y 2)) let function_with_NEL = "(define (foo (x y)) \u{0085} (+ 1 2 3) \u{0085} (- 1 2 3))"; let function_with_LS = "(define (foo (x y)) \u{2028} (+ 1 2 3) \u{2028} (- 1 2 3))"; let function_with_PS = "(define (foo (x y)) \u{2029} (+ 1 2 3) \u{2029} (- 1 2 3))"; - // good case let function_with_LF = "(define (foo (x y)) \n (+ 1 2 3) \n (- 1 2 3))"; + let string_with_invalid_escape = r#" + "hello\eworld" + "#; + let ascii_string_with_unicode_escape = r#" + "hello\u{1F436}world" + "#; + let string_with_valid_escape = r#" + "hello\nworld" + "#; + let string_with_valid_double_escape = r#" + "hello\\eworld" + "#; + let string_with_multiple_slashes = r#" + "hello\\\"world" + "#; assert!(match ast::parser::parse(&split_tokens).unwrap_err().err { ParseErrors::SeparatorExpected(_) => true, _ => false }); @@ -773,6 +831,20 @@ r#"z (let ((x 1) (y 2)) ParseErrors::FailedParsingRemainder(_) => true, _ => false }); ast::parser::parse(&function_with_LF).unwrap(); - } -} + assert!(match ast::parser::parse(&string_with_invalid_escape).unwrap_err().err { + ParseErrors::InvalidEscaping => true, _ => false }); + + assert!(match ast::parser::parse(&ascii_string_with_unicode_escape).unwrap_err().err { + ParseErrors::InvalidEscaping => true, _ => false }); + + assert!(match ast::parser::parse(&string_with_valid_escape).unwrap()[0].pre_expr { + PreSymbolicExpressionType::AtomValue(Value::Sequence(SequenceData::String(CharType::ASCII(ref v)))) if v.data.len() == 11 => true, _ => false }); + + assert!(match ast::parser::parse(&string_with_valid_double_escape).unwrap()[0].pre_expr { + PreSymbolicExpressionType::AtomValue(Value::Sequence(SequenceData::String(CharType::ASCII(ref v)))) if v.data.len() == 12 => true, _ => false }); + + assert!(match ast::parser::parse(&string_with_multiple_slashes).unwrap()[0].pre_expr { + PreSymbolicExpressionType::AtomValue(Value::Sequence(SequenceData::String(CharType::ASCII(ref v)))) if v.data.len() == 12 => true, _ => false }); + } +} \ No newline at end of file diff --git a/src/vm/docs/mod.rs b/src/vm/docs/mod.rs index 1738f9e59..60453e4aa 100644 --- a/src/vm/docs/mod.rs +++ b/src/vm/docs/mod.rs @@ -394,10 +394,8 @@ has to be a literal function name.", (fold * (list 2 2 2) 0) ;; Returns 0 ;; calculates (- 11 (- 7 (- 3 2))) (fold - (list 3 7 11) 2) ;; Returns 5 -(fold concat \"cdef\" \"ab\") ;; Returns 0x666564636162 - ;; hex form of \"fedcab\" -(fold concat (list \"cd\" \"ef\") \"ab\") ;; Returns 0x656663646162 - ;; hex form of \"efcdab\"" +(fold concat \"cdef\" \"ab\") ;; Returns \"fedcab\" +(fold concat (list \"cd\" \"ef\") \"ab\") ;; Returns \"efcdab\"" }; const CONCAT_API: SpecialAPI = SpecialAPI { @@ -406,8 +404,7 @@ const CONCAT_API: SpecialAPI = SpecialAPI { signature: "(concat buff-a buff-b)", description: "The `concat` function takes two buffers or two lists with the same entry type, and returns a concatenated buffer or list of the same entry type, with max_len = max_len_a + max_len_b.", - example: "(concat \"hello \" \"world\") ;; Returns 0x68656c6c6f20776f726c64 - ;; hex form of \"hello world\"" + example: "(concat \"hello \" \"world\") ;; Returns \"hello world\"" }; const APPEND_API: SpecialAPI = SpecialAPI { @@ -426,8 +423,8 @@ const ASSERTS_MAX_LEN_API: SpecialAPI = SpecialAPI { description: "The `as-max-len?` function takes a length N (must be a literal) and a buffer or list argument, which must be typed as a list or buffer of length M and outputs that same list or buffer, but typed with max length N. -This function returns an optional type with the resulting iterable. If the input iterable is less than -or equal to the supplied max-len, it returns `(some )`, otherwise it returns `none`.", +This function returns an optional type with the resulting sequence. If the input sequence is less than +or equal to the supplied max-len, it returns `(some )`, otherwise it returns `none`.", example: "(as-max-len? (list 2 2 2) u3) ;; Returns (some (2 2 2)) (as-max-len? (list 1 2 3) u2) ;; Returns none" }; @@ -477,7 +474,7 @@ const FETCH_ENTRY_API: SpecialAPI = SpecialAPI { The value is looked up using `key-tuple`. If there is no value associated with that key in the data map, the function returns a `none` option. Otherwise, it returns `(some value)`.", - example: "(define-map names-map ((name (buff 10))) ((id int))) + example: "(define-map names-map ((name (string-ascii 10))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) (map-get? names-map (tuple (name \"blockstack\"))) ;; Returns (some (tuple (id 1337))) (map-get? names-map ((name \"blockstack\"))) ;; Same command, using a shorthand for constructing the tuple @@ -494,7 +491,7 @@ with the key, the function overwrites that existing association. Note: the `value-tuple` requires 1 additional byte for storage in the materialized blockchain state, and therefore the maximum size of a value that may be inserted into a map is MAX_CLARITY_VALUE - 1.", - example: "(define-map names-map ((name (buff 10))) ((id int))) + example: "(define-map names-map ((name (string-ascii 10))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) ;; Returns true (map-set names-map ((name \"blockstack\")) ((id 1337))) ;; Same command, using a shorthand for constructing the tuple ", @@ -511,7 +508,7 @@ this key in the data map, the function returns `false`. Note: the `value-tuple` requires 1 additional byte for storage in the materialized blockchain state, and therefore the maximum size of a value that may be inserted into a map is MAX_CLARITY_VALUE - 1.", - example: "(define-map names-map ((name (buff 10))) ((id int))) + example: "(define-map names-map ((name (string-ascii 10))) ((id int))) (map-insert names-map { name: \"blockstack\" } { id: 1337 }) ;; Returns true (map-insert names-map { name: \"blockstack\" } { id: 1337 }) ;; Returns false (map-insert names-map ((name \"blockstack\")) ((id 1337))) ;; Same command, using a shorthand for constructing the tuple @@ -525,7 +522,7 @@ const DELETE_ENTRY_API: SpecialAPI = SpecialAPI { description: "The `map-delete` function removes the value associated with the input key for the given map. If an item exists and is removed, the function returns `true`. If a value did not exist for this key in the data map, the function returns `false`.", - example: "(define-map names-map ((name (buff 10))) ((id int))) + example: "(define-map names-map ((name (string-ascii 10))) ((id int))) (map-insert names-map { name: \"blockstack\" } { id: 1337 }) ;; Returns true (map-delete names-map { name: \"blockstack\" }) ;; Returns true (map-delete names-map { name: \"blockstack\" }) ;; Returns false @@ -551,7 +548,7 @@ const TUPLE_GET_API: SpecialAPI = SpecialAPI { description: "The `get` function fetches the value associated with a given key from the supplied typed tuple. If an `Optional` value is supplied as the inputted tuple, `get` returns an `Optional` type of the specified key in the tuple. If the supplied option is a `(none)` option, get returns `(none)`.", - example: "(define-map names-map ((name (buff 12))) ((id int))) + example: "(define-map names-map ((name (string-ascii 12))) ((id int))) (map-insert names-map { name: \"blockstack\" } { id: 1337 }) ;; Returns true (get id (tuple (name \"blockstack\") (id 1337))) ;; Returns 1337 (get id (map-get? names-map (tuple (name \"blockstack\")))) ;; Returns (some 1337) @@ -686,9 +683,9 @@ option. If the argument is a response type, and the argument is an `(ok ...)` re the inner value of the `ok`. If the supplied argument is either an `(err ...)` or a `(none)` value, `unwrap!` _returns_ `thrown-value` from the current function and exits the current control-flow.", example: " -(define-map names-map ((name (buff 12))) ((id int))) +(define-map names-map ((name (string-ascii 12))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) -(define-private (get-name-or-err (name (buff 12))) +(define-private (get-name-or-err (name (string-ascii 12))) (let ((raw-name (unwrap! (map-get? names-map { name: name }) (err 1)))) (ok raw-name))) @@ -706,7 +703,7 @@ option. If the argument is a response type, and the argument is an `(ok ...)` re the inner value of the `ok`. If the supplied argument is either an `(err ...)` or a `none` value, `try!` _returns_ either `none` or the `(err ...)` value from the current function and exits the current control-flow.", example: " -(define-map names-map ((name (buff 12))) ((id int))) +(define-map names-map ((name (string-ascii 12))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) (try! (map-get? names-map { name: \"blockstack\" })) ;; Returns (tuple (id 1337)) (define-private (checked-even (x int)) @@ -730,7 +727,7 @@ option. If the argument is a response type, and the argument is an `(ok ...)` re the inner value of the `ok`. If the supplied argument is either an `(err ...)` or a `(none)` value, `unwrap` throws a runtime error, aborting any further processing of the current transaction.", example: " -(define-map names-map ((name (buff 12))) ((id int))) +(define-map names-map ((name (string-ascii 12))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) (unwrap-panic (map-get? names-map { name: \"blockstack\" })) ;; Returns (tuple (id 1337)) (unwrap-panic (map-get? names-map { name: \"non-existant\" })) ;; Throws a runtime exception @@ -799,7 +796,7 @@ is untyped, you should use `unwrap-panic` or `unwrap-err-panic` instead of `matc (add-10 (some 5)) ;; returns 15 (add-10 none) ;; returns 10 -(define-private (add-or-pass-err (x (response int (buff 10))) (to-add int)) +(define-private (add-or-pass-err (x (response int (string-ascii 10))) (to-add int)) (match x value (+ to-add value) err-value (err err-value))) @@ -815,7 +812,7 @@ const DEFAULT_TO_API: SpecialAPI = SpecialAPI { a `(some ...)` option, it returns the inner value of the option. If the second argument is a `(none)` value, `default-to` it returns the value of `default-value`.", example: " -(define-map names-map ((name (buff 12))) ((id int))) +(define-map names-map ((name (string-ascii 12))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) (default-to 0 (get id (map-get? names-map (tuple (name \"blockstack\"))))) ;; Returns 1337 (default-to 0 (get id (map-get? names-map (tuple (name \"non-existant\"))))) ;; Returns 0 @@ -868,7 +865,7 @@ const IS_NONE_API: SpecialAPI = SpecialAPI { description: "`is-none` tests a supplied option value, returning `true` if the option value is `(none)`, and `false` if it is a `(some ...)`.", example: " -(define-map names-map ((name (buff 12))) ((id int))) +(define-map names-map ((name (string-ascii 12))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) (is-none (get id (map-get? names-map { name: \"blockstack\" }))) ;; Returns false (is-none (get id (map-get? names-map { name: \"non-existant\" }))) ;; Returns true" @@ -891,7 +888,7 @@ const IS_SOME_API: SpecialAPI = SpecialAPI { description: "`is-some` tests a supplied option value, returning `true` if the option value is `(some ...)`, and `false` if it is a `none`.", example: " -(define-map names-map ((name (buff 12))) ((id int))) +(define-map names-map ((name (string-ascii 12))) ((id int))) (map-set names-map { name: \"blockstack\" } { id: 1337 }) (is-some (get id (map-get? names-map { name: \"blockstack\" }))) ;; Returns true (is-some (get id (map-get? names-map { name: \"non-existant\" }))) ;; Returns false" @@ -1176,7 +1173,7 @@ If an asset identified by `asset-identifier` _already exists_, this function wil Otherwise, on successfuly mint, it returns `(ok true)`. ", example: " -(define-non-fungible-token stackaroo (buff 40)) +(define-non-fungible-token stackaroo (string-ascii 40)) (nft-mint? stackaroo \"Roo\" 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF) ;; returns (ok true) " }; @@ -1189,7 +1186,7 @@ const GET_OWNER: SpecialAPI = SpecialAPI { The asset type must have been defined using `define-non-fungible-token`, and the supplied `asset-identifier` must be of the same type specified in that definition.", example: " -(define-non-fungible-token stackaroo (buff 40)) +(define-non-fungible-token stackaroo (string-ascii 40)) (nft-mint? stackaroo \"Roo\" 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF) (nft-get-owner? stackaroo \"Roo\") ;; Returns (some SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF) (nft-get-owner? stackaroo \"Too\") ;; Returns none @@ -1248,7 +1245,7 @@ one of the following error codes: `(err u3)` -- asset identified by asset-identifier does not exist ", example: " -(define-non-fungible-token stackaroo (buff 40)) +(define-non-fungible-token stackaroo (string-ascii 40)) (nft-mint? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR) (nft-transfer? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF) ;; returns (ok true) (nft-transfer? stackaroo \"Roo\" 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SPAXYA5XS51713FDTQ8H94EJ4V579CXMTRNBZKSF) ;; returns (err u1) diff --git a/src/vm/functions/database.rs b/src/vm/functions/database.rs index 91b01bf43..196208e96 100644 --- a/src/vm/functions/database.rs +++ b/src/vm/functions/database.rs @@ -4,7 +4,7 @@ use std::cmp; use vm::functions::tuples; use vm::functions::tuples::TupleDefinitionType::{Implicit, Explicit}; -use vm::types::{Value, OptionalData, BuffData, PrincipalData, BlockInfoProperty, TypeSignature, BUFF_32}; +use vm::types::{Value, SequenceData, OptionalData, BuffData, PrincipalData, BlockInfoProperty, TypeSignature, BUFF_32}; use vm::representations::{SymbolicExpression, SymbolicExpressionType}; use vm::errors::{CheckErrors, InterpreterError, RuntimeErrorType, InterpreterResult as Result, check_argument_count, check_arguments_at_least}; @@ -192,7 +192,7 @@ pub fn special_at_block(args: &[SymbolicExpression], runtime_cost!(cost_functions::AT_BLOCK, env, 0)?; let bhh = match eval(&args[0], env, &context)? { - Value::Buffer(BuffData { data }) => { + Value::Sequence(SequenceData::Buffer(BuffData { data })) => { if data.len() != 32 { return Err(RuntimeErrorType::BadBlockHash(data).into()) } else { @@ -353,19 +353,19 @@ pub fn special_get_block_info(args: &[SymbolicExpression], }, BlockInfoProperty::VrfSeed => { let vrf_seed = env.global_context.database.get_block_vrf_seed(height_value); - Value::Buffer(BuffData { data: vrf_seed.as_bytes().to_vec() }) + Value::Sequence(SequenceData::Buffer(BuffData { data: vrf_seed.as_bytes().to_vec() })) }, BlockInfoProperty::HeaderHash => { let header_hash = env.global_context.database.get_block_header_hash(height_value); - Value::Buffer(BuffData { data: header_hash.as_bytes().to_vec() }) + Value::Sequence(SequenceData::Buffer(BuffData { data: header_hash.as_bytes().to_vec() })) }, BlockInfoProperty::BurnchainHeaderHash => { let burnchain_header_hash = env.global_context.database.get_burnchain_block_header_hash(height_value); - Value::Buffer(BuffData { data: burnchain_header_hash.as_bytes().to_vec() }) + Value::Sequence(SequenceData::Buffer(BuffData { data: burnchain_header_hash.as_bytes().to_vec() })) }, BlockInfoProperty::IdentityHeaderHash => { let id_header_hash = env.global_context.database.get_index_block_header_hash(height_value); - Value::Buffer(BuffData { data: id_header_hash.as_bytes().to_vec() }) + Value::Sequence(SequenceData::Buffer(BuffData { data: id_header_hash.as_bytes().to_vec() })) }, BlockInfoProperty::MinerAddress => { let miner_address = env.global_context.database.get_miner_address(height_value); diff --git a/src/vm/functions/iterables.rs b/src/vm/functions/iterables.rs deleted file mode 100644 index bdf39cb44..000000000 --- a/src/vm/functions/iterables.rs +++ /dev/null @@ -1,219 +0,0 @@ -use vm::costs::{cost_functions, CostOverflowingMath}; -use vm::errors::{CheckErrors, RuntimeErrorType, InterpreterResult as Result, check_argument_count}; -use vm::types::{Value, ListData, signatures::ListTypeData, TypeSignature::BoolType, TypeSignature}; -use vm::representations::{SymbolicExpression, SymbolicExpressionType}; -use vm::{LocalContext, Environment, eval, apply, lookup_function}; -use std::convert::TryInto; -use std::cmp; - -pub fn list_cons(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { - let eval_tried: Result> = - args.iter().map(|x| eval(x, env, context)).collect(); - let args = eval_tried?; - - let mut arg_size = 0; - for a in args.iter() { - arg_size = arg_size.cost_overflow_add(a.size().into())?; - } - - runtime_cost!(cost_functions::LIST_CONS, env, arg_size)?; - - Value::list_from(args) -} - -pub fn special_filter(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { - check_argument_count(2, args)?; - - runtime_cost!(cost_functions::FILTER, env, 0)?; - - let function_name = args[0].match_atom() - .ok_or(CheckErrors::ExpectedName)?; - - let function = lookup_function(&function_name, env)?; - let iterable = eval(&args[1], env, context)?; - - match iterable { - Value::List(mut list) => { - let mut filtered_vec = Vec::new(); - for x in list.data.drain(..) { - let argument = [ SymbolicExpression::atom_value(x.clone()) ]; - let filter_eval = apply(&function, &argument, env, context)?; - if let Value::Bool(include) = filter_eval { - if include { - filtered_vec.push(x); - } // else, filter out. - } else { - return Err(CheckErrors::TypeValueError(BoolType, filter_eval).into()) - } - } - Value::list_with_type(filtered_vec, list.type_signature) - }, - Value::Buffer(mut buff) => { - let mut filtered_vec = Vec::new(); - for x in buff.data.drain(..) { - let v = Value::buff_from(vec![x.clone()])?; - let argument = [ SymbolicExpression::atom_value(v) ]; - let filter_eval = apply(&function, &argument, env, context)?; - if let Value::Bool(include) = filter_eval { - if include { - filtered_vec.push(x); - } // else, filter out. - } else { - return Err(CheckErrors::TypeValueError(BoolType, filter_eval).into()) - } - } - Value::buff_from(filtered_vec) - }, - _ => Err(CheckErrors::ExpectedListOrBuffer(TypeSignature::type_of(&iterable)).into()) - } -} - -pub fn special_fold(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { - check_argument_count(3, args)?; - - runtime_cost!(cost_functions::FILTER, env, 0)?; - - let function_name = args[0].match_atom() - .ok_or(CheckErrors::ExpectedName)?; - - let function = lookup_function(&function_name, env)?; - let iterable = eval(&args[1], env, context)?; - let initial = eval(&args[2], env, context)?; - - let mapped_args: Vec<_> = match iterable { - Value::List(mut list) => { - list.data.drain(..).map(|x| { - SymbolicExpression::atom_value(x) - }).collect() - }, - Value::Buffer(mut buff) => { - buff.data.drain(..).map(|x| { - SymbolicExpression::atom_value(Value::buff_from_byte(x)) - }).collect() - }, - _ => return Err(CheckErrors::ExpectedListOrBuffer(TypeSignature::type_of(&iterable)).into()) - }; - mapped_args.iter().try_fold(initial, |acc, x| { - apply(&function, &[x.clone(), SymbolicExpression::atom_value(acc)], env, context) - }) -} - -pub fn special_map(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { - check_argument_count(2, args)?; - - runtime_cost!(cost_functions::MAP, env, 0)?; - - let function_name = args[0].match_atom() - .ok_or(CheckErrors::ExpectedName)?; - let iterable = eval(&args[1], env, context)?; - let function = lookup_function(&function_name, env)?; - - let mapped_args: Vec<_> = match iterable { - Value::List(mut list) => { - list.data.drain(..).map(|x| { - vec![SymbolicExpression::atom_value(x)] - }).collect() - }, - Value::Buffer(mut buff) => { - buff.data.drain(..).map(|x| { - vec![SymbolicExpression::atom_value(Value::buff_from_byte(x))] - }).collect() - }, - _ => return Err(CheckErrors::ExpectedListOrBuffer(TypeSignature::type_of(&iterable)).into()) - }; - let mapped_vec: Result> = - mapped_args.iter().map(|argument| apply(&function, &argument, env, context)).collect(); - Value::list_from(mapped_vec?) -} - -pub fn special_append(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { - check_argument_count(2, args)?; - - let iterable = eval(&args[0], env, context)?; - match iterable { - Value::List(list) => { - let element = eval(&args[1], env, context)?; - let ListData { mut data, type_signature } = list; - let (entry_type, size) = type_signature.destruct(); - let element_type = TypeSignature::type_of(&element); - runtime_cost!(cost_functions::APPEND, env, - u64::from(cmp::max(entry_type.size(), element_type.size())))?; - if entry_type.is_no_type() { - assert_eq!(size, 0); - return Value::list_from(vec![ element ]) - } - if let Ok(next_entry_type) = TypeSignature::least_supertype(&entry_type, &element_type) { - let next_type_signature = ListTypeData::new_list(next_entry_type, size + 1)?; - data.push(element); - Ok(Value::List(ListData { - type_signature: next_type_signature, - data })) - } else { - Err(CheckErrors::TypeValueError(entry_type, element).into()) - } - }, - _ => Err(CheckErrors::ExpectedListApplication.into()) - } -} - -pub fn special_concat(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { - check_argument_count(2, args)?; - - let lhs = eval(&args[0], env, context)?; - let rhs = eval(&args[1], env, context)?; - - runtime_cost!(cost_functions::CONCAT, env, - u64::from(lhs.size()).cost_overflow_add( - u64::from(rhs.size()))?)?; - - match (lhs, rhs) { - (Value::List(lhs_data), Value::List(mut rhs_data)) => { - let mut data = lhs_data.data; - data.append(&mut rhs_data.data); - Value::list_from(data) - }, - (Value::Buffer(lhs_data), Value::Buffer(mut rhs_data)) => { - let mut data = lhs_data.data; - data.append(&mut rhs_data.data); - Value::buff_from(data) - }, - (_, _) => { - Err(RuntimeErrorType::BadTypeConstruction.into()) - } - } -} - -pub fn special_as_max_len(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { - check_argument_count(2, args)?; - - let mut iterable = eval(&args[0], env, context)?; - - runtime_cost!(cost_functions::AS_MAX_LEN, env, 0)?; - - if let Some(Value::UInt(expected_len)) = args[1].match_literal_value() { - let iterable_len = match iterable { - Value::List(ref list) => list.data.len(), - Value::Buffer(ref buff) => buff.data.len(), - _ => return Err(CheckErrors::ExpectedListOrBuffer(TypeSignature::type_of(&iterable)).into()) - }; - if iterable_len as u128 > *expected_len { - Ok(Value::none()) - } else { - if let Value::List(ref mut list) = iterable { - list.type_signature.reduce_max_len(*expected_len as u32); - } - Ok(Value::some(iterable)?) - } - } else { - let actual_len = eval(&args[1], env, context)?; - Err(CheckErrors::TypeError(TypeSignature::UIntType, TypeSignature::type_of(&actual_len)).into()) - } -} - -pub fn native_len(iterable: Value) -> Result { - match iterable { - Value::List(list) => Ok(Value::UInt(list.data.len() as u128)), - Value::Buffer(buff) => Ok(Value::UInt(buff.data.len() as u128)), - _ => Err(CheckErrors::ExpectedListOrBuffer(TypeSignature::type_of(&iterable)).into()) - } -} diff --git a/src/vm/functions/mod.rs b/src/vm/functions/mod.rs index bf9024421..0af2268b3 100644 --- a/src/vm/functions/mod.rs +++ b/src/vm/functions/mod.rs @@ -1,6 +1,6 @@ pub mod define; pub mod tuples; -mod iterables; +mod sequences; mod arithmetic; mod boolean; mod database; @@ -8,7 +8,7 @@ mod options; mod assets; use vm::errors::{Error, CheckErrors, RuntimeErrorType, ShortReturnType, InterpreterResult as Result, check_argument_count, check_arguments_at_least}; -use vm::types::{Value, PrincipalData, ResponseData, TypeSignature}; +use vm::types::{Value, SequenceData, CharType, PrincipalData, ResponseData, TypeSignature}; use vm::callables::{CallableType, NativeHandle}; use vm::representations::{SymbolicExpression, SymbolicExpressionType, ClarityName}; use vm::representations::SymbolicExpressionType::{List, Atom}; @@ -116,14 +116,14 @@ pub fn lookup_reserved_functions(name: &str) -> Option { Let => SpecialFunction("special_let", &special_let), FetchVar => SpecialFunction("special_var-get", &database::special_fetch_variable), SetVar => SpecialFunction("special_set-var", &database::special_set_variable), - Map => SpecialFunction("special_map", &iterables::special_map), - Filter => SpecialFunction("special_filter", &iterables::special_filter), - Fold => SpecialFunction("special_fold", &iterables::special_fold), - Concat => SpecialFunction("special_concat", &iterables::special_concat), - AsMaxLen => SpecialFunction("special_as_max_len", &iterables::special_as_max_len), - Append => SpecialFunction("special_append", &iterables::special_append), - Len => NativeFunction("native_len", NativeHandle::SingleArg(&iterables::native_len), cost_functions::LEN), - ListCons => SpecialFunction("special_list_cons", &iterables::list_cons), + Map => SpecialFunction("special_map", &sequences::special_map), + Filter => SpecialFunction("special_filter", &sequences::special_filter), + Fold => SpecialFunction("special_fold", &sequences::special_fold), + Concat => SpecialFunction("special_concat", &sequences::special_concat), + AsMaxLen => SpecialFunction("special_as_max_len", &sequences::special_as_max_len), + Append => SpecialFunction("special_append", &sequences::special_append), + Len => NativeFunction("native_len", NativeHandle::SingleArg(&sequences::native_len), cost_functions::LEN), + ListCons => SpecialFunction("special_list_cons", &sequences::list_cons), FetchEntry => SpecialFunction("special_map-get?", &database::special_fetch_entry), SetEntry => SpecialFunction("special_set-entry", &database::special_set_entry), InsertEntry => SpecialFunction("special_insert-entry", &database::special_insert_entry), @@ -200,7 +200,7 @@ macro_rules! native_hash_func { let bytes = match input { Value::Int(value) => Ok(value.to_le_bytes().to_vec()), Value::UInt(value) => Ok(value.to_le_bytes().to_vec()), - Value::Buffer(value) => Ok(value.data), + Value::Sequence(SequenceData::Buffer(value)) => Ok(value.data), _ => Err(CheckErrors::UnionTypeValueError(vec![TypeSignature::IntType, TypeSignature::UIntType, TypeSignature::max_buffer()], input)) }?; let hash = <$module>::from_data(&bytes); diff --git a/src/vm/functions/sequences.rs b/src/vm/functions/sequences.rs new file mode 100644 index 000000000..2d924d511 --- /dev/null +++ b/src/vm/functions/sequences.rs @@ -0,0 +1,177 @@ +use vm::costs::{cost_functions, CostOverflowingMath}; +use vm::errors::{CheckErrors, RuntimeErrorType, InterpreterResult as Result, check_argument_count}; +use vm::types::{Value, SequenceData, CharType, ListData, signatures::ListTypeData, TypeSignature::BoolType, TypeSignature}; +use vm::representations::{SymbolicExpression, SymbolicExpressionType}; +use vm::{LocalContext, Environment, CallableType, eval, apply, lookup_function}; +use std::convert::TryInto; +use std::cmp; + +pub fn list_cons(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { + let eval_tried: Result> = + args.iter().map(|x| eval(x, env, context)).collect(); + let args = eval_tried?; + + let mut arg_size = 0; + for a in args.iter() { + arg_size = arg_size.cost_overflow_add(a.size().into())?; + } + + runtime_cost!(cost_functions::LIST_CONS, env, arg_size)?; + + Value::list_from(args) +} + +pub fn special_filter(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { + check_argument_count(2, args)?; + + runtime_cost!(cost_functions::FILTER, env, 0)?; + + let function_name = args[0].match_atom() + .ok_or(CheckErrors::ExpectedName)?; + + let mut sequence = eval(&args[1], env, context)?; + let function = lookup_function(&function_name, env)?; + + match sequence { + Value::Sequence(ref mut sequence_data) => { + sequence_data.filter(&mut |atom_value: SymbolicExpression| { + let argument = [atom_value]; + let filter_eval = apply(&function, &argument, env, context)?; + if let Value::Bool(include) = filter_eval { + return Ok(include); + } else { + return Err(CheckErrors::TypeValueError(BoolType, filter_eval).into()) + } + })?; + }, + _ => return Err(CheckErrors::ExpectedSequence(TypeSignature::type_of(&sequence)).into()) + }; + Ok(sequence) +} + +pub fn special_fold(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { + check_argument_count(3, args)?; + + runtime_cost!(cost_functions::FOLD, env, 0)?; + + let function_name = args[0].match_atom() + .ok_or(CheckErrors::ExpectedName)?; + + let function = lookup_function(&function_name, env)?; + let mut sequence = eval(&args[1], env, context)?; + let initial = eval(&args[2], env, context)?; + + match sequence { + Value::Sequence(ref mut sequence_data) => { + sequence_data.atom_values() + .into_iter() + .try_fold(initial, |acc, x| { + apply(&function, &[x, SymbolicExpression::atom_value(acc)], env, context) + }) + }, + _ => Err(CheckErrors::ExpectedSequence(TypeSignature::type_of(&sequence)).into()) + } +} + +pub fn special_map(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { + check_argument_count(2, args)?; + + runtime_cost!(cost_functions::MAP, env, 0)?; + + let function_name = args[0].match_atom() + .ok_or(CheckErrors::ExpectedName)?; + let mut sequence = eval(&args[1], env, context)?; + let function = lookup_function(&function_name, env)?; + + let mapped_sequence: Vec<_> = match sequence { + Value::Sequence(ref mut sequence_data) => { + sequence_data.atom_values() + .into_iter() + .map(|argument| apply(&function, &[argument], env, context)) + .collect() + }, + _ => Err(CheckErrors::ExpectedSequence(TypeSignature::type_of(&sequence)).into()) + }?; + Value::list_from(mapped_sequence) +} + +pub fn special_append(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { + check_argument_count(2, args)?; + + let sequence = eval(&args[0], env, context)?; + match sequence { + Value::Sequence(SequenceData::List(list)) => { + let element = eval(&args[1], env, context)?; + let ListData { mut data, type_signature } = list; + let (entry_type, size) = type_signature.destruct(); + let element_type = TypeSignature::type_of(&element); + runtime_cost!(cost_functions::APPEND, env, + u64::from(cmp::max(entry_type.size(), element_type.size())))?; + if entry_type.is_no_type() { + assert_eq!(size, 0); + return Value::list_from(vec![ element ]) + } + if let Ok(next_entry_type) = TypeSignature::least_supertype(&entry_type, &element_type) { + let next_type_signature = ListTypeData::new_list(next_entry_type, size + 1)?; + data.push(element); + Ok(Value::Sequence(SequenceData::List(ListData { + type_signature: next_type_signature, + data }))) + } else { + Err(CheckErrors::TypeValueError(entry_type, element).into()) + } + }, + _ => Err(CheckErrors::ExpectedListApplication.into()) + } +} + +pub fn special_concat(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { + check_argument_count(2, args)?; + + let mut wrapped_seq = eval(&args[0], env, context)?; + let mut other_wrapped_seq = eval(&args[1], env, context)?; + + runtime_cost!(cost_functions::CONCAT, env, + u64::from(wrapped_seq.size()).cost_overflow_add( + u64::from(other_wrapped_seq.size()))?)?; + + match (&mut wrapped_seq, &mut other_wrapped_seq) { + (Value::Sequence(ref mut seq), Value::Sequence(ref mut other_seq)) => seq.append(other_seq), + _ => Err(RuntimeErrorType::BadTypeConstruction.into()) + }?; + + Ok(wrapped_seq) +} + +pub fn special_as_max_len(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result { + check_argument_count(2, args)?; + + let mut sequence = eval(&args[0], env, context)?; + + runtime_cost!(cost_functions::AS_MAX_LEN, env, 0)?; + + if let Some(Value::UInt(expected_len)) = args[1].match_literal_value() { + let sequence_len = match sequence { + Value::Sequence(ref sequence_data) => sequence_data.len() as u128, + _ => return Err(CheckErrors::ExpectedSequence(TypeSignature::type_of(&sequence)).into()) + }; + if sequence_len > *expected_len { + Ok(Value::none()) + } else { + if let Value::Sequence(SequenceData::List(ref mut list)) = sequence { + list.type_signature.reduce_max_len(*expected_len as u32); + } + Ok(Value::some(sequence)?) + } + } else { + let actual_len = eval(&args[1], env, context)?; + Err(CheckErrors::TypeError(TypeSignature::UIntType, TypeSignature::type_of(&actual_len)).into()) + } +} + +pub fn native_len(sequence: Value) -> Result { + match sequence { + Value::Sequence(sequence_data) => Ok(Value::UInt(sequence_data.len() as u128)), + _ => Err(CheckErrors::ExpectedSequence(TypeSignature::type_of(&sequence)).into()) + } +} diff --git a/src/vm/tests/datamaps.rs b/src/vm/tests/datamaps.rs index aa4dd3ab1..1e9ad33a3 100644 --- a/src/vm/tests/datamaps.rs +++ b/src/vm/tests/datamaps.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; use vm::errors::{Error, CheckErrors, RuntimeErrorType, ShortReturnType}; -use vm::types::{Value, TupleData, TypeSignature, QualifiedContractIdentifier, StandardPrincipalData, ListData, TupleTypeSignature}; +use vm::types::{Value, SequenceData, TupleData, TypeSignature, QualifiedContractIdentifier, StandardPrincipalData, ListData, TupleTypeSignature}; use vm::contexts::{OwnedEnvironment}; use vm::database::MemoryBackingStore; use vm::execute; @@ -307,7 +307,7 @@ fn test_get_list_max_len() { let actual_value = execute(&contract_src).unwrap().unwrap(); match actual_value { - Value::List(ListData { data, type_signature }) => { + Value::Sequence(SequenceData::List(ListData { data, type_signature })) => { assert_eq!(vec![Value::Int(1), Value::Int(2), Value::Int(3)], data); assert_eq!("(list 10 int)", &format!("{}", TypeSignature::from(type_signature))); @@ -317,12 +317,12 @@ fn test_get_list_max_len() { } #[test] -fn test_set_buffer_variable() { +fn test_set_string_variable() { let contract_src = r#" - (define-data-var name (buff 5) "alice") + (define-data-var name (string-ascii 5) "alice") (define-private (get-name) (var-get name)) - (define-private (set-name (new-name (buff 5))) + (define-private (set-name (new-name (string-ascii 5))) (if (var-set name new-name) new-name (get-name))) @@ -331,9 +331,9 @@ fn test_set_buffer_variable() { let mut contract_src = contract_src.to_string(); contract_src.push_str("(list (get-name) (set-name \"celia\") (get-name))"); let expected = Value::list_from(vec![ - Value::buff_from("alice".to_string().into_bytes()).unwrap(), - Value::buff_from("celia".to_string().into_bytes()).unwrap(), - Value::buff_from("celia".to_string().into_bytes()).unwrap(), + Value::string_ascii_from_bytes("alice".to_string().into_bytes()).unwrap(), + Value::string_ascii_from_bytes("celia".to_string().into_bytes()).unwrap(), + Value::string_ascii_from_bytes("celia".to_string().into_bytes()).unwrap(), ]); assert_executes(expected, &contract_src); } @@ -528,10 +528,10 @@ fn lists_system() { fn tuples_system() { let test1 = "(define-map tuples ((name int)) - ((contents (tuple (name (buff 5)) - (owner (buff 5)))))) + ((contents (tuple (name (string-ascii 5)) + (owner (string-ascii 5)))))) - (define-private (add-tuple (name int) (content (buff 5))) + (define-private (add-tuple (name int) (content (string-ascii 5))) (map-insert tuples (tuple (name name)) (tuple (contents (tuple (name content) @@ -565,8 +565,8 @@ fn tuples_system() { test_bad_tuple_5.push_str("(map-delete tuples (tuple (names 1)))"); let expected = || { - let buff1 = Value::buff_from("abcde".to_string().into_bytes())?; - let buff2 = Value::buff_from("abcd".to_string().into_bytes())?; + let buff1 = Value::string_ascii_from_bytes("abcde".to_string().into_bytes())?; + let buff2 = Value::string_ascii_from_bytes("abcd".to_string().into_bytes())?; Value::list_from(vec![buff1, buff2]) }; diff --git a/src/vm/tests/large_contract.rs b/src/vm/tests/large_contract.rs index af1c86c29..c7da8cecc 100644 --- a/src/vm/tests/large_contract.rs +++ b/src/vm/tests/large_contract.rs @@ -36,7 +36,7 @@ pub fn rollback_log_memory_test() { &StacksBlockId([0 as u8; 32]), &NULL_HEADER_DB); - let define_data_var = "(define-data-var XZ (buff 1048576) \"a\")"; + let define_data_var = "(define-data-var XZ (buff 1048576) 0x00)"; let mut contract = define_data_var.to_string(); for i in 0..20 { @@ -78,7 +78,7 @@ pub fn let_memory_test() { &StacksBlockId([0 as u8; 32]), &NULL_HEADER_DB); - let define_data_var = "(define-constant buff-0 \"a\")"; + let define_data_var = "(define-constant buff-0 0x00)"; let mut contract = define_data_var.to_string(); for i in 0..20 { @@ -121,7 +121,7 @@ pub fn argument_memory_test() { &StacksBlockId([0 as u8; 32]), &NULL_HEADER_DB); - let define_data_var = "(define-constant buff-0 \"a\")"; + let define_data_var = "(define-constant buff-0 0x00)"; let mut contract = define_data_var.to_string(); for i in 0..20 { @@ -165,7 +165,7 @@ pub fn fcall_memory_test() { &StacksBlockId([0 as u8; 32]), &NULL_HEADER_DB); - let define_data_var = "(define-constant buff-0 \"a\")"; + let define_data_var = "(define-constant buff-0 0x00)"; let mut contract = define_data_var.to_string(); for i in 0..20 { @@ -237,7 +237,7 @@ pub fn ccall_memory_test() { &StacksBlockId([0 as u8; 32]), &NULL_HEADER_DB); - let define_data_var = "(define-constant buff-0 \"a\")\n"; + let define_data_var = "(define-constant buff-0 0x00)\n"; let mut contract = define_data_var.to_string(); for i in 0..20 { diff --git a/src/vm/tests/mod.rs b/src/vm/tests/mod.rs index 9485479a9..c399540c6 100644 --- a/src/vm/tests/mod.rs +++ b/src/vm/tests/mod.rs @@ -15,7 +15,7 @@ use chainstate::stacks::StacksBlockId; mod forking; mod assets; mod events; -mod iterables; +mod sequences; mod defines; mod simple_apply_eval; mod datamaps; diff --git a/src/vm/tests/iterables.rs b/src/vm/tests/sequences.rs similarity index 56% rename from src/vm/tests/iterables.rs rename to src/vm/tests/sequences.rs index b370f33c7..23afdc989 100644 --- a/src/vm/tests/iterables.rs +++ b/src/vm/tests/sequences.rs @@ -1,6 +1,6 @@ use vm::types::{Value, TypeSignature}; -use vm::types::TypeSignature::{IntType, UIntType, BoolType, ListType, BufferType}; -use vm::types::signatures::{ListTypeData}; +use vm::types::TypeSignature::{IntType, UIntType, BoolType, SequenceType}; +use vm::types::signatures::{ListTypeData, SequenceSubtype}; use vm::execute; use vm::errors::{CheckErrors, RuntimeErrorType, Error}; @@ -35,6 +35,177 @@ fn test_simple_list_admission() { }); } +#[test] +fn test_string_ascii_admission() { + let defines = + "(define-private (set-name (x (string-ascii 11))) x)"; + let t1 = format!("{} (set-name \"hello world\")", defines); + + let expected = Value::string_ascii_from_bytes("hello world".into()).unwrap(); + + assert_eq!(expected, execute(&t1).unwrap().unwrap()); +} + +#[test] +fn test_string_utf8_admission() { + let defines = + "(define-private (set-name (x (string-utf8 14))) x)"; + let t1 = format!("{} (set-name u\"my 2 \\u{{c2a2}} (cents)\")", defines); + + let expected = Value::string_utf8_from_string_utf8_literal("my 2 \\u{c2a2} (cents)".into()).unwrap(); + + assert_eq!(expected, execute(&t1).unwrap().unwrap()); +} + +#[test] +fn test_string_ascii_map() { + let defines = + "(define-private (replace-a-with-b (c (string-ascii 1))) (if (is-eq \"a\" c) \"b\" c))"; + let t1 = format!("{} (map replace-a-with-b \"ababab\")", defines); + + let expected = Value::list_from(vec![ + Value::string_ascii_from_bytes("b".into()).unwrap(), + Value::string_ascii_from_bytes("b".into()).unwrap(), + Value::string_ascii_from_bytes("b".into()).unwrap(), + Value::string_ascii_from_bytes("b".into()).unwrap(), + Value::string_ascii_from_bytes("b".into()).unwrap(), + Value::string_ascii_from_bytes("b".into()).unwrap(), + ]).unwrap(); + + assert_eq!(expected, execute(&t1).unwrap().unwrap()); +} + +#[test] +fn test_string_utf8_map() { + let defines = + "(define-private (replace-dog-with-fox (c (string-utf8 1))) (if (is-eq u\"\\u{1F436}\" c) u\"\\u{1F98A}\" c))"; + let t1 = format!("{} (map replace-dog-with-fox u\"fox \\u{{1F436}}\")", defines); + + let expected = Value::list_from(vec![ + Value::string_utf8_from_bytes("f".into()).unwrap(), + Value::string_utf8_from_bytes("o".into()).unwrap(), + Value::string_utf8_from_bytes("x".into()).unwrap(), + Value::string_utf8_from_bytes(" ".into()).unwrap(), + Value::string_utf8_from_bytes("🦊".into()).unwrap(), + ]).unwrap(); + + assert_eq!(expected, execute(&t1).unwrap().unwrap()); +} + +#[test] +fn test_string_ascii_filter() { + let defines = + "(define-private (remove-a (c (string-ascii 1))) (not (is-eq \"a\" c)))"; + let t1 = format!("{} (filter remove-a \"ababab\")", defines); + + let expected = Value::string_ascii_from_bytes("bbb".into()).unwrap(); + + assert_eq!(expected, execute(&t1).unwrap().unwrap()); +} + +#[test] +fn test_string_utf8_filter() { + let defines = + "(define-private (keep-dog (c (string-utf8 1))) (is-eq u\"\\u{1F436}\" c))"; + let t1 = format!("{} (filter keep-dog u\"fox \\u{{1F98A}} \\u{{1F436}}\")", defines); + + let expected = Value::string_utf8_from_bytes("🐶".into()).unwrap(); + + assert_eq!(expected, execute(&t1).unwrap().unwrap()); +} + +#[test] +fn test_string_ascii_fold() { + let test1 = + "(define-private (merge (x (string-ascii 1)) (acc (string-ascii 5))) (concat acc x)) + (fold merge (list \"A\" \"B\" \"C\" \"D\" \"E\") \"\")"; + + let expected = Value::string_ascii_from_bytes("ABCDE".into()).unwrap(); + + assert_eq!(expected, execute(test1).unwrap().unwrap()); +} + +#[test] +fn test_string_utf8_fold() { + let test1 = + "(define-private (build-face-palm (x (string-utf8 1)) (acc (string-utf8 5))) (concat acc x)) + (fold build-face-palm (list u\"\\u{1F926}\" u\"\\u{1F3FC}\" u\"\\u{200D}\" u\"\\u{2642}\" u\"\\u{FE0F}\") u\"\")"; + + let expected = Value::string_utf8_from_bytes("🤦🏼‍♂️".into()).unwrap(); + + assert_eq!(expected, execute(test1).unwrap().unwrap()); +} + +#[test] +fn test_string_ascii_concat() { + let test1 = "(concat (concat \"A\" \"B\") \"C\")"; + + let expected = Value::string_ascii_from_bytes("ABC".into()).unwrap(); + + assert_eq!(expected, execute(test1).unwrap().unwrap()); +} + +#[test] +fn test_string_utf8_concat() { + let test1 = + "(concat (concat (concat (concat u\"\\u{1F926}\" u\"\\u{1F3FC}\") u\"\\u{200D}\") u\"\\u{2642}\") u\"\\u{FE0F}\")"; + + let expected = Value::string_utf8_from_bytes("🤦🏼‍♂️".into()).unwrap(); + + assert_eq!(expected, execute(test1).unwrap().unwrap()); +} + +#[test] +fn test_string_ascii_get_len() { + let test1 = "(len \"ABCDE\")"; + let expected = Value::UInt(5); + assert_eq!(expected, execute(test1).unwrap().unwrap()); +} + +#[test] +fn test_string_utf8_get_len() { + let test1 = "(len u\"ABCDE\\u{1F926}\\u{1F3FC}\\u{200D}\\u{2642}\\u{FE0F}\")"; + let expected = Value::UInt(10); + assert_eq!(expected, execute(test1).unwrap().unwrap()); +} + +#[test] +fn test_string_ascii_max_len() { + let tests = [ + "(as-max-len? \"ABC\" u3)", + "(as-max-len? \"ABC\" u2)", + "(as-max-len? \"ABC\" u4)", + ]; + + let expected = [ + Value::some(Value::string_ascii_from_bytes("ABC".into()).unwrap()).unwrap(), + Value::none(), + Value::some(Value::string_ascii_from_bytes("ABC".into()).unwrap()).unwrap(), + ]; + + for (test, expected) in tests.iter().zip(expected.iter()) { + assert_eq!(expected.clone(), execute(test).unwrap().unwrap()); + } +} + +#[test] +fn test_string_utf8_max_len() { + let tests = [ + "(as-max-len? u\"ABCDE\\u{1F926}\\u{1F3FC}\\u{200D}\\u{2642}\\u{FE0F}\" u10)", + "(as-max-len? u\"ABCDE\\u{1F926}\\u{1F3FC}\\u{200D}\\u{2642}\\u{FE0F}\" u9)", + "(as-max-len? u\"ABCDE\\u{1F926}\\u{1F3FC}\\u{200D}\\u{2642}\\u{FE0F}\" u11)"]; + + let expected = [ + Value::some(Value::string_utf8_from_bytes("ABCDE🤦🏼‍♂️".into()).unwrap()).unwrap(), + Value::none(), + Value::some(Value::string_utf8_from_bytes("ABCDE🤦🏼‍♂️".into()).unwrap()).unwrap(), + ]; + + for (test, expected) in tests.iter().zip(expected.iter()) { + assert_eq!(expected.clone(), execute(test).unwrap().unwrap()); + } +} + #[test] fn test_simple_map_list() { let test1 = @@ -138,36 +309,36 @@ fn test_simple_list_concat() { #[test] fn test_simple_buff_concat() { let tests = [ - "(concat \"012\" \"34\")", - "(concat \"\" \"\")", - "(concat \"\" \"1\")", - "(concat \"1\" \"\")"]; + "(concat 0x303132 0x3334)", + "(concat 0x00 0x00)", + "(concat 0x00 0x31)", + "(concat 0x31 0x00)"]; let expected = [ Value::buff_from(vec![48, 49, 50, 51, 52]).unwrap(), - Value::buff_from(vec![]).unwrap(), - Value::buff_from(vec![49]).unwrap(), - Value::buff_from(vec![49]).unwrap()]; + Value::buff_from(vec![0, 0]).unwrap(), + Value::buff_from(vec![0, 49]).unwrap(), + Value::buff_from(vec![49, 0]).unwrap()]; for (test, expected) in tests.iter().zip(expected.iter()) { assert_eq!(expected.clone(), execute(test).unwrap().unwrap()); } assert_eq!( - execute("(concat \"1\" 3)").unwrap_err(), + execute("(concat 0x31 3)").unwrap_err(), RuntimeErrorType::BadTypeConstruction.into()); assert_eq!( - execute("(concat \"1\" (list 1))").unwrap_err(), + execute("(concat 0x31 (list 1))").unwrap_err(), RuntimeErrorType::BadTypeConstruction.into()); } #[test] fn test_simple_buff_assert_max_len() { let tests = [ - "(as-max-len? \"123\" u3)", - "(as-max-len? \"123\" u2)", - "(as-max-len? \"123\" u5)"]; + "(as-max-len? 0x313233 u3)", + "(as-max-len? 0x313233 u2)", + "(as-max-len? 0x313233 u5)"]; let expected = [ Value::some(Value::buff_from(vec![49, 50, 51]).unwrap()).unwrap(), @@ -179,20 +350,20 @@ fn test_simple_buff_assert_max_len() { } assert_eq!( - execute("(as-max-len? \"123\")").unwrap_err(), + execute("(as-max-len? 0x313233)").unwrap_err(), CheckErrors::IncorrectArgumentCount(2, 1).into()); assert_eq!( - execute("(as-max-len? \"123\" 3)").unwrap_err(), + execute("(as-max-len? 0x313233 3)").unwrap_err(), CheckErrors::TypeError(UIntType, IntType).into()); assert_eq!( execute("(as-max-len? 1 u3)").unwrap_err(), - CheckErrors::ExpectedListOrBuffer(IntType).into()); + CheckErrors::ExpectedSequence(IntType).into()); assert_eq!( - execute("(as-max-len? \"123\" \"1\")").unwrap_err(), - CheckErrors::TypeError(UIntType, BufferType(1_u32.try_into().unwrap())).into()); + execute("(as-max-len? 0x313233 0x31)").unwrap_err(), + CheckErrors::TypeError(UIntType, SequenceType(SequenceSubtype::BufferType(1_u32.try_into().unwrap()))).into()); } #[test] @@ -215,8 +386,8 @@ fn test_simple_list_assert_max_len() { #[test] fn test_simple_map_buffer() { let test1 = - "(define-private (incr (x (buff 1))) \"1\") - (map incr \"0000\")"; + "(define-private (incr (x (buff 1))) 0x31) + (map incr 0x30303030)"; let expected = Value::list_from(vec![ Value::buff_from(vec![49]).unwrap(), @@ -226,18 +397,17 @@ fn test_simple_map_buffer() { assert_eq!(expected, execute(test1).unwrap().unwrap()); } - #[test] fn test_simple_filter_list() { let test1 = "(define-private (test (x int)) (is-eq 0 (mod x 2))) (filter test (list 1 2 3 4 5))"; let bad_tests = [ - "(filter 123 (list 123))", // must have function name supplied + "(filter 123 (list 123))", // must have function name supplied "(filter not (list 123) 3)", // must be 2 args - "(filter +)", // must be 2 args - "(filter not false)", // must supply list - "(filter - (list 1 2 3))"]; // must return bool + "(filter +)", // must be 2 args + "(filter not false)", // must supply list + "(filter - (list 1 2 3))"]; // must return bool let expected = Value::list_from(vec![ @@ -253,8 +423,8 @@ fn test_simple_filter_list() { #[test] fn test_simple_filter_buffer() { - let test1 = "(define-private (test (x (buff 1))) (not (is-eq x \"0\"))) - (filter test \"000123\")"; + let test1 = "(define-private (test (x (buff 1))) (not (is-eq x 0x30))) + (filter test 0x303030313233)"; let expected = Value::buff_from(vec![49, 50, 51]).unwrap(); assert_eq!(expected, execute(test1).unwrap().unwrap()); @@ -263,28 +433,27 @@ fn test_simple_filter_buffer() { #[test] fn test_list_tuple_admission() { let test = - "(define-private (bufferize (x int)) (if (is-eq x 1) \"abc\" \"ab\")) + "(define-private (bufferize (x int)) (if (is-eq x 1) 0x616263 0x6162)) (define-private (tuplize (x int)) (tuple (value (bufferize x)))) (map tuplize (list 0 1 0 1 0 1))"; let expected_type = - "(list (tuple (value \"012\")) - (tuple (value \"012\")) - (tuple (value \"012\")) - (tuple (value \"012\")) - (tuple (value \"012\")) - (tuple (value \"012\")))"; + "(list (tuple (value 0x303132)) + (tuple (value 0x303132)) + (tuple (value 0x303132)) + (tuple (value 0x303132)) + (tuple (value 0x303132)) + (tuple (value 0x303132)))"; let not_expected_type = - "(list (tuple (value \"01\")) - (tuple (value \"02\")) - (tuple (value \"12\")) - (tuple (value \"12\")) - (tuple (value \"01\")) - (tuple (value \"02\")))"; + "(list (tuple (value 0x3031)) + (tuple (value 0x3032)) + (tuple (value 0x3132)) + (tuple (value 0x3132)) + (tuple (value 0x3031)) + (tuple (value 0x3032)))"; - let result_type = TypeSignature::type_of(&execute(test).unwrap().unwrap()); let expected_type = TypeSignature::type_of(&execute(expected_type).unwrap().unwrap()); let testing_value = &execute(not_expected_type).unwrap().unwrap(); @@ -307,11 +476,11 @@ fn test_simple_folds_list() { } #[test] -fn test_simple_folds_buffer() { +fn test_simple_folds_string() { let tests = - ["(define-private (get-len (x (buff 1)) (acc int)) (+ acc 1)) + ["(define-private (get-len (x (string-ascii 1)) (acc int)) (+ acc 1)) (fold get-len \"blockstack\" 0)", - "(define-private (slice (x (buff 1)) (acc (tuple (limit uint) (cursor uint) (data (buff 10))))) + "(define-private (slice (x (string-ascii 1)) (acc (tuple (limit uint) (cursor uint) (data (string-ascii 10))))) (if (< (get cursor acc) (get limit acc)) (let ((data (default-to (get data acc) (as-max-len? (concat (get data acc) x) u10)))) (tuple (limit (get limit acc)) (cursor (+ u1 (get cursor acc))) (data data))) @@ -320,7 +489,7 @@ fn test_simple_folds_buffer() { let expected = [ Value::Int(10), - Value::buff_from(vec![48, 49, 50, 51, 52]).unwrap()]; + Value::string_ascii_from_bytes(vec![48, 49, 50, 51, 52]).unwrap()]; for (test, expected) in tests.iter().zip(expected.iter()) { assert_eq!(expected.clone(), execute(test).unwrap().unwrap()); diff --git a/src/vm/tests/simple_apply_eval.rs b/src/vm/tests/simple_apply_eval.rs index 02344790b..0580fb18e 100644 --- a/src/vm/tests/simple_apply_eval.rs +++ b/src/vm/tests/simple_apply_eval.rs @@ -2,6 +2,7 @@ use vm::{eval, execute as vm_execute}; use vm::database::MemoryBackingStore; use vm::errors::{CheckErrors, ShortReturnType, RuntimeErrorType, Error}; use vm::{Value, LocalContext, ContractContext, GlobalContext, Environment, CallStack}; +use vm::types::{SequenceData}; use vm::contexts::{OwnedEnvironment}; use vm::callables::DefinedFunction; use vm::types::{TypeSignature, BuffData, QualifiedContractIdentifier}; @@ -55,13 +56,13 @@ fn test_simple_let() { #[test] fn test_sha256() { let sha256_evals = [ - "(sha256 \"\")", + "(sha256 0x)", "(sha256 0)", - "(sha256 \"The quick brown fox jumps over the lazy dog\")", + "(sha256 0x54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67)", // The quick brown fox jumps over the lazy dog ]; fn to_buffer(hex: &str) -> Value { - return Value::Buffer(BuffData { data: hex_bytes(hex).unwrap() }); + return Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes(hex).unwrap() })); } let expectations = [ @@ -77,14 +78,14 @@ fn test_sha256() { #[test] fn test_sha512() { let sha512_evals = [ - "(sha512 \"\")", + "(sha512 0x)", "(sha512 0)", - "(sha512 \"The quick brown fox jumps over the lazy dog\")", + "(sha512 0x54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67)", // The quick brown fox jumps over the lazy dog ]; fn p_to_hex(val: Value) -> String { match val { - Value::Buffer(BuffData { data }) => to_hex(&data), + Value::Sequence(SequenceData::Buffer(BuffData { data })) => to_hex(&data), _ => panic!("Failed") } } @@ -102,14 +103,14 @@ fn test_sha512() { #[test] fn test_sha512trunc256() { let sha512_evals = [ - "(sha512/256 \"\")", + "(sha512/256 0x)", "(sha512/256 0)", - "(sha512/256 \"The quick brown fox jumps over the lazy dog\")", + "(sha512/256 0x54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67)", // The quick brown fox jumps over the lazy dog ]; fn p_to_hex(val: Value) -> String { match val { - Value::Buffer(BuffData { data }) => to_hex(&data), + Value::Sequence(SequenceData::Buffer(BuffData { data })) => to_hex(&data), _ => panic!("Failed") } } @@ -127,13 +128,13 @@ fn test_sha512trunc256() { #[test] fn test_keccak256() { let keccak256_evals = [ - "(keccak256 \"\")", + "(keccak256 0x)", "(keccak256 0)", - "(keccak256 \"The quick brown fox jumps over the lazy dog\")", + "(keccak256 0x54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67)", // The quick brown fox jumps over the lazy dog ]; fn to_buffer(hex: &str) -> Value { - return Value::Buffer(BuffData { data: hex_bytes(hex).unwrap() }); + return Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes(hex).unwrap() })); } let expectations = [ @@ -265,7 +266,8 @@ fn test_concat_append_supertype() { ]; tests.iter().zip(expectations.iter()) - .for_each(|(program, expectation)| assert_eq!(expectation.clone(), execute(program))); + .for_each(|(program, expectation)| { + assert_eq!(expectation.clone(), execute(program))}); } #[test] diff --git a/src/vm/tests/traits.rs b/src/vm/tests/traits.rs index 3c011fe3a..15b61c648 100644 --- a/src/vm/tests/traits.rs +++ b/src/vm/tests/traits.rs @@ -1,6 +1,4 @@ use vm::types::{Value, TypeSignature, QualifiedContractIdentifier, ResponseData, PrincipalData}; -use vm::types::TypeSignature::{IntType, UIntType, BoolType, ListType, BufferType}; -use vm::types::signatures::{ListTypeData}; use vm::contexts::{OwnedEnvironment,GlobalContext, Environment}; use vm::execute as vm_execute; use vm::errors::{CheckErrors, RuntimeErrorType, Error}; diff --git a/src/vm/types/mod.rs b/src/vm/types/mod.rs index 600cbcb5d..11fa0597a 100644 --- a/src/vm/types/mod.rs +++ b/src/vm/types/mod.rs @@ -4,6 +4,9 @@ pub mod signatures; use std::{fmt, cmp}; use std::convert::{TryInto, TryFrom}; use std::collections::BTreeMap; +use std::{char, str}; + +use regex::Regex; use address::c32; use vm::representations::{ClarityName, ContractName, SymbolicExpression, SymbolicExpressionType}; @@ -12,8 +15,8 @@ use util::hash; pub use vm::types::signatures::{ TupleTypeSignature, AssetIdentifier, FixedFunction, FunctionSignature, - TypeSignature, FunctionType, ListTypeData, FunctionArg, parse_name_type_pairs, - BUFF_64, BUFF_32, BUFF_20, BufferLength + TypeSignature, SequenceSubtype, StringSubtype, FunctionType, ListTypeData, FunctionArg, parse_name_type_pairs, + BUFF_64, BUFF_32, BUFF_20, BUFF_1, BufferLength, StringUTF8Length }; pub const MAX_VALUE_SIZE: u32 = 1024 * 1024; // 1MB @@ -174,14 +177,257 @@ pub enum Value { Int(i128), UInt(u128), Bool(bool), - Buffer(BuffData), - List(ListData), + Sequence(SequenceData), Principal(PrincipalData), Tuple(TupleData), Optional(OptionalData), Response(ResponseData), } +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum SequenceData { + Buffer(BuffData), + List(ListData), + String(CharType), +} + +impl SequenceData { + + pub fn atom_values(&mut self) -> Vec { + match self { + SequenceData::Buffer(ref mut data) => data.atom_values(), + SequenceData::List(ref mut data) => data.atom_values(), + SequenceData::String(CharType::ASCII(ref mut data)) => data.atom_values(), + SequenceData::String(CharType::UTF8(ref mut data)) => data.atom_values(), + } + } + + pub fn len(&self) -> usize { + match &self { + SequenceData::Buffer(data) => data.items().len(), + SequenceData::List(data) => data.items().len(), + SequenceData::String(CharType::ASCII(data)) => data.items().len(), + SequenceData::String(CharType::UTF8(data)) => data.items().len(), + } + } + + pub fn filter(&mut self, filter: &mut F) -> Result<()> where F: FnMut(SymbolicExpression) -> Result { + + // Note: this macro can probably get removed once + // ```Vec::drain_filter(&mut self, filter: F) -> DrainFilter``` + // is available in rust stable channel (experimental at this point). + macro_rules! drain_filter { + ($data:expr, $seq_type:ident) => { + let mut i = 0; + while i != $data.data.len() { + let atom_value = SymbolicExpression::atom_value($seq_type::to_value(&$data.data[i])); + match filter(atom_value) { + Ok(res) if res == false => { $data.data.remove(i); }, + Ok(_) => { i += 1; }, + Err(err) => return Err(err), + } + } + }; + } + + match self { + SequenceData::Buffer(ref mut data) => { + drain_filter!(data, BuffData); + }, + SequenceData::List(ref mut data) => { + drain_filter!(data, ListData); + }, + SequenceData::String(CharType::ASCII(ref mut data)) => { + drain_filter!(data, ASCIIData); + }, + SequenceData::String(CharType::UTF8(ref mut data)) => { + drain_filter!(data, UTF8Data); + }, + } + Ok(()) + } + + pub fn append(&mut self, other_seq: &mut SequenceData) -> Result<()> { + + match (self, other_seq) { + (SequenceData::List(ref mut inner_data), SequenceData::List(ref mut other_inner_data)) => { + inner_data.append(other_inner_data) + }, + (SequenceData::Buffer(ref mut inner_data), SequenceData::Buffer(ref mut other_inner_data)) => { + inner_data.append(other_inner_data) + }, + (SequenceData::String(CharType::ASCII(ref mut inner_data)), SequenceData::String(CharType::ASCII(ref mut other_inner_data))) => { + inner_data.append(other_inner_data) + }, + (SequenceData::String(CharType::UTF8(ref mut inner_data)), SequenceData::String(CharType::UTF8(ref mut other_inner_data))) => { + inner_data.append(other_inner_data) + }, + _ => Err(RuntimeErrorType::BadTypeConstruction.into()) + }?; + Ok(()) + } +} + +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum CharType { + UTF8(UTF8Data), + ASCII(ASCIIData), +} + +impl fmt::Display for CharType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + CharType::ASCII(string) => write!(f, "{}", string), + CharType::UTF8(string) => write!(f, "{}", string), + } + } +} + +impl fmt::Debug for CharType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ASCIIData { + pub data: Vec, +} + +impl fmt::Display for ASCIIData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut escaped_str = String::new(); + for c in self.data.iter() { + let escaped_char = format!("{}", std::ascii::escape_default(*c)); + escaped_str.push_str(&escaped_char); + } + write!(f, "{}", format!("\"{}\"", escaped_str)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct UTF8Data { + pub data: Vec>, +} + +impl fmt::Display for UTF8Data { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut result = String::new(); + for c in self.data.iter() { + if c.len() > 1 { + // We escape extended charset + result.push_str(&format!("\\u{{{}}}", hash::to_hex(&c[..]))); + } else { + // We render an ASCII char, escaped + let escaped_char = format!("{}", std::ascii::escape_default(c[0])); + result.push_str(&escaped_char); + } + } + write!(f, "{}", format!("u\"{}\"", result)) + } +} + +pub trait SequencedValue { + + fn type_signature(&self) -> TypeSignature; + + fn items(&self) -> &Vec; + + fn drained_items(&mut self) -> Vec; + + fn to_value(v: &T) -> Value; + + fn atom_values(&mut self) -> Vec { + self.drained_items().iter().map(|item| { + SymbolicExpression::atom_value(Self::to_value(&item)) + }).collect() + } +} + +impl SequencedValue for ListData { + + fn items(&self) -> &Vec { + &self.data + } + + fn drained_items(&mut self) -> Vec { + self.data.drain(..).collect() + } + + fn type_signature(&self) -> TypeSignature { + TypeSignature::SequenceType(SequenceSubtype::ListType(self.type_signature.clone())) + } + + fn to_value(v: &Value) -> Value { + v.clone() + } +} + +impl SequencedValue for BuffData { + + fn items(&self) -> &Vec { + &self.data + } + + fn drained_items(&mut self) -> Vec { + self.data.drain(..).collect() + } + + fn type_signature(&self) -> TypeSignature { + let buff_length = BufferLength::try_from(self.data.len()) + .expect("ERROR: Too large of a buffer successfully constructed."); + TypeSignature::SequenceType(SequenceSubtype::BufferType(buff_length)) + } + + fn to_value(v: &u8) -> Value { + Value::buff_from_byte(*v) + } +} + +impl SequencedValue for ASCIIData { + + fn items(&self) -> &Vec { + &self.data + } + + fn drained_items(&mut self) -> Vec { + self.data.drain(..).collect() + } + + fn type_signature(&self) -> TypeSignature { + let buff_length = BufferLength::try_from(self.data.len()) + .expect("ERROR: Too large of a buffer successfully constructed."); + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(buff_length))) + } + + fn to_value(v: &u8) -> Value { + Value::string_ascii_from_bytes(vec![*v]) + .expect("ERROR: Invalid ASCII string successfully constructed") + } +} + +impl SequencedValue> for UTF8Data { + + fn items(&self) -> &Vec> { + &self.data + } + + fn drained_items(&mut self) -> Vec> { + self.data.drain(..).collect() + } + + fn type_signature(&self) -> TypeSignature { + let str_len = StringUTF8Length::try_from(self.data.len()) + .expect("ERROR: Too large of a buffer successfully constructed."); + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(str_len))) + } + + fn to_value(v: &Vec) -> Value { + Value::string_utf8_from_bytes(v.clone()) + .expect("ERROR: Invalid UTF8 string successfully constructed") + } +} + define_named_enum!(BlockInfoProperty { Time("time"), VrfSeed("vrf-seed"), @@ -319,7 +565,7 @@ impl Value { } } - Ok(Value::List(ListData { data: list_data, type_signature: expected_type })) + Ok(Value::Sequence(SequenceData::List(ListData { data: list_data, type_signature: expected_type }))) } pub fn list_from(list_data: Vec) -> Result { @@ -330,18 +576,81 @@ impl Value { // this is a problem _if_ the static analyzer cannot already prevent // this case. This applies to all the constructor size checks. let type_sig = TypeSignature::construct_parent_list_type(&list_data)?; - Ok(Value::List(ListData { data: list_data, type_signature: type_sig })) + Ok(Value::Sequence(SequenceData::List(ListData { data: list_data, type_signature: type_sig }))) } pub fn buff_from(buff_data: Vec) -> Result { // check the buffer size BufferLength::try_from(buff_data.len())?; // construct the buffer - Ok(Value::Buffer(BuffData { data: buff_data })) + Ok(Value::Sequence(SequenceData::Buffer(BuffData { data: buff_data }))) } pub fn buff_from_byte(byte: u8) -> Value { - Value::Buffer(BuffData { data: vec![byte] }) + Value::Sequence(SequenceData::Buffer(BuffData { data: vec![byte] })) + } + + pub fn string_ascii_from_bytes(bytes: Vec) -> Result { + // check the string size + BufferLength::try_from(bytes.len())?; + + for b in bytes.iter() { + if !b.is_ascii_alphanumeric() && !b.is_ascii_punctuation() && !b.is_ascii_whitespace() { + return Err(CheckErrors::InvalidCharactersDetected.into()); + } + } + // construct the string + Ok(Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data: bytes })))) + } + + pub fn string_utf8_from_string_utf8_literal(tokenized_str: String) -> Result { + let wrapped_codepoints_matcher = Regex::new("^\\\\u\\{(?P[[:xdigit:]]+)\\}").unwrap(); + let mut window = tokenized_str.as_str(); + let mut cursor = 0; + let mut data: Vec> = vec![]; + while !window.is_empty() { + if let Some(captures) = wrapped_codepoints_matcher.captures(window) { + let matched = captures.name("value").unwrap(); + let scalar_value = window[matched.start()..matched.end()].to_string(); + let unicode_char = { + let u = u32::from_str_radix(&scalar_value, 16).unwrap(); + let c = char::from_u32(u).unwrap(); + let mut encoded_char: Vec = vec![0; c.len_utf8()]; + c.encode_utf8(&mut encoded_char[..]); + encoded_char + }; + + data.push(unicode_char); + cursor += scalar_value.len() + 4; + } else { + let ascii_char = window[0..1].to_string().into_bytes(); + data.push(ascii_char); + cursor += 1; + } + // check the string size + StringUTF8Length::try_from(data.len())?; + + window = &tokenized_str[cursor..]; + } + // construct the string + Ok(Value::Sequence(SequenceData::String(CharType::UTF8(UTF8Data { data })))) + } + + pub fn string_utf8_from_bytes(bytes: Vec) -> Result { + let validated_utf8_str = match str::from_utf8(&bytes) { + Ok(string) => string, + _ => return Err(CheckErrors::InvalidCharactersDetected.into()) + }; + let mut data = vec![]; + for char in validated_utf8_str.chars() { + let mut encoded_char: Vec = vec![0; char.len_utf8()]; + char.encode_utf8(&mut encoded_char[..]); + data.push(encoded_char); + } + // check the string size + StringUTF8Length::try_from(data.len())?; + + Ok(Value::Sequence(SequenceData::String(CharType::UTF8(UTF8Data { data })))) } } @@ -349,12 +658,49 @@ impl BuffData { pub fn len(&self) -> BufferLength { self.data.len().try_into().unwrap() } + + fn append(&mut self, other_seq: &mut BuffData) -> Result<()> { + self.data.append(&mut other_seq.data); + Ok(()) + } } impl ListData { pub fn len(&self) -> u32 { self.data.len().try_into().unwrap() } + + fn append(&mut self, other_seq: &mut ListData) -> Result<()> { + let entry_type_a = self.type_signature.get_list_item_type(); + let entry_type_b = other_seq.type_signature.get_list_item_type(); + let entry_type = TypeSignature::factor_out_no_type(&entry_type_a, &entry_type_b)?; + let max_len = self.type_signature.get_max_len() + other_seq.type_signature.get_max_len(); + self.type_signature = ListTypeData::new_list(entry_type, max_len)?; + self.data.append(&mut other_seq.data); + Ok(()) + } +} + +impl ASCIIData { + fn append(&mut self, other_seq: &mut ASCIIData) -> Result<()> { + self.data.append(&mut other_seq.data); + Ok(()) + } + + pub fn len(&self) -> BufferLength { + self.data.len().try_into().unwrap() + } +} + +impl UTF8Data { + fn append(&mut self, other_seq: &mut UTF8Data) -> Result<()> { + self.data.append(&mut other_seq.data); + Ok(()) + } + + pub fn len(&self) -> BufferLength { + self.data.len().try_into().unwrap() + } } impl fmt::Display for OptionalData { @@ -393,12 +739,13 @@ impl fmt::Display for Value { Value::Int(int) => write!(f, "{}", int), Value::UInt(int) => write!(f, "u{}", int), Value::Bool(boolean) => write!(f, "{}", boolean), - Value::Buffer(vec_bytes) => write!(f, "0x{}", &vec_bytes), Value::Tuple(data) => write!(f, "{}", data), Value::Principal(principal_data) => write!(f, "{}", principal_data), Value::Optional(opt_data) => write!(f, "{}", opt_data), Value::Response(res_data) => write!(f, "{}", res_data), - Value::List(list_data) => { + Value::Sequence(SequenceData::Buffer(vec_bytes)) => write!(f, "0x{}", &vec_bytes), + Value::Sequence(SequenceData::String(string)) => write!(f, "{}", string), + Value::Sequence(SequenceData::List(list_data)) => { write!(f, "(")?; for (ix, v) in list_data.data.iter().enumerate() { if ix > 0 { diff --git a/src/vm/types/serialization.rs b/src/vm/types/serialization.rs index 6f712e484..8f9e2cd36 100644 --- a/src/vm/types/serialization.rs +++ b/src/vm/types/serialization.rs @@ -1,7 +1,7 @@ use vm::errors::{RuntimeErrorType, InterpreterResult, InterpreterError, IncomparableError, Error as ClarityError, CheckErrors}; -use vm::types::{Value, StandardPrincipalData, OptionalData, PrincipalData, BufferLength, MAX_VALUE_SIZE, - BOUND_VALUE_SERIALIZATION_BYTES, +use vm::types::{Value, SequenceSubtype, StringSubtype, StandardPrincipalData, OptionalData, PrincipalData, BufferLength, StringUTF8Length, MAX_VALUE_SIZE, + BOUND_VALUE_SERIALIZATION_BYTES, SequenceData, CharType, TypeSignature, TupleData, QualifiedContractIdentifier, ResponseData}; use vm::database::{ClaritySerializable, ClarityDeserializable}; use vm::representations::{ClarityName, ContractName, MAX_STRING_LEN}; @@ -15,7 +15,7 @@ use serde_json::{Value as JSONValue}; use util::hash::{hex_bytes, to_hex}; use util::retry::{BoundReader}; -use std::{error, fmt}; +use std::{error, fmt, str}; use std::io::{Write, Read}; @@ -88,7 +88,9 @@ define_u8_enum!(TypePrefix { OptionalNone = 9, OptionalSome = 10, List = 11, - Tuple = 12 + Tuple = 12, + StringASCII = 13, + StringUTF8 = 14 }); impl From<&PrincipalData> for TypePrefix { @@ -104,11 +106,12 @@ impl From<&PrincipalData> for TypePrefix { impl From<&Value> for TypePrefix { fn from(v: &Value) -> TypePrefix { use super::Value::*; + use super::SequenceData::*; + use super::CharType; match v { Int(_) => TypePrefix::Int, UInt(_) => TypePrefix::UInt, - Buffer(_) => TypePrefix::Buffer, Bool(value) => { if *value { TypePrefix::BoolTrue @@ -126,8 +129,11 @@ impl From<&Value> for TypePrefix { }, Optional(OptionalData{ data: None }) => TypePrefix::OptionalNone, Optional(OptionalData{ data: Some(_) }) => TypePrefix::OptionalSome, - List(_) => TypePrefix::List, Tuple(_) => TypePrefix::Tuple, + Sequence(Buffer(_)) => TypePrefix::Buffer, + Sequence(List(_)) => TypePrefix::List, + Sequence(String(CharType::ASCII(_))) => TypePrefix::StringASCII, + Sequence(String(CharType::UTF8(_))) => TypePrefix::StringUTF8, } } } @@ -286,7 +292,7 @@ impl Value { if let Some(x) = expected_type { let passed_test = match x { - TypeSignature::BufferType(expected_len) => { + TypeSignature::SequenceType(SequenceSubtype::BufferType(expected_len)) => { u32::from(&buffer_len) <= u32::from(expected_len) }, _ => false @@ -378,7 +384,7 @@ impl Value { let (list_type, entry_type) = match expected_type { None => (None, None), - Some(TypeSignature::ListType(list_type)) => { + Some(TypeSignature::SequenceType(SequenceSubtype::ListType(list_type))) => { if len > list_type.get_max_len() { return Err(SerializationError::DeserializeExpected( expected_type.unwrap().clone())) @@ -447,23 +453,74 @@ impl Value { .map_err(|_| "Illegal tuple type".into()) .map(Value::from) } - } + }, + TypePrefix::StringASCII => { + let mut buffer_len = [0; 4]; + r.read_exact(&mut buffer_len)?; + let buffer_len = BufferLength::try_from( + u32::from_be_bytes(buffer_len))?; + + if let Some(x) = expected_type { + let passed_test = match x { + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(expected_len))) => { + u32::from(&buffer_len) <= u32::from(expected_len) + }, + _ => false + }; + if !passed_test { + return Err(SerializationError::DeserializeExpected(x.clone())) + } + } + + let mut data = vec![0; u32::from(buffer_len) as usize]; + + r.read_exact(&mut data[..])?; + + // can safely unwrap, because the string length was _already_ checked. + Ok(Value::string_ascii_from_bytes(data).unwrap()) + }, + TypePrefix::StringUTF8 => { + let mut total_len = [0; 4]; + r.read_exact(&mut total_len)?; + let total_len = BufferLength::try_from( + u32::from_be_bytes(total_len))?; + + let mut data: Vec = vec![0; u32::from(total_len) as usize]; + + r.read_exact(&mut data[..])?; + + let value = Value::string_utf8_from_bytes(data) + .map_err(|_| "Illegal string_utf8 type".into()); + + if let Some(x) = expected_type { + let passed_test = match (x, &value) { + (TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(expected_len))), + Ok(Value::Sequence(SequenceData::String(CharType::UTF8(utf8))))) => { + utf8.data.len() as u32 <= u32::from(expected_len) + }, + _ => false + }; + if !passed_test { + return Err(SerializationError::DeserializeExpected(x.clone())) + } + } + + value + }, } } pub fn serialize_write(&self, w: &mut W) -> std::io::Result<()> { use super::Value::*; + use super::SequenceData::{self, *}; + use super::CharType::*; use super::PrincipalData::*; w.write_all(&[TypePrefix::from(self) as u8])?; match self { Int(value) => w.write_all(&value.to_be_bytes())?, UInt(value) => w.write_all(&value.to_be_bytes())?, - Buffer(value) => { - w.write_all(&(u32::from(value.len()).to_be_bytes()))?; - w.write_all(&value.data)? - } Principal(Standard(data)) => { data.serialize_write(w)? }, @@ -481,12 +538,28 @@ impl Value { Optional(OptionalData{ data: Some(value) }) => { value.serialize_write(w)?; }, - List(data) => { + Sequence(List(data)) => { w.write_all(&data.len().to_be_bytes())?; for item in data.data.iter() { item.serialize_write(w)?; } }, + Sequence(Buffer(value)) => { + w.write_all(&(u32::from(value.len()).to_be_bytes()))?; + w.write_all(&value.data)? + }, + Sequence(SequenceData::String(UTF8(value))) => { + let total_len: u32 = value.data.iter() + .fold(0u32, |len, c| len + c.len() as u32); + w.write_all(&(total_len.to_be_bytes()))?; + for bytes in value.data.iter() { + w.write_all(&bytes)? + } + }, + Sequence(SequenceData::String(ASCII(value))) => { + w.write_all(&(u32::from(value.len()).to_be_bytes()))?; + w.write_all(&value.data)? + }, Tuple(data) => { w.write_all(&u32::try_from(data.data_map.len()) .unwrap() @@ -565,7 +638,7 @@ mod tests { use vm::types::TypeSignature::{IntType, BoolType}; fn buff_type(size: u32) -> TypeSignature { - TypeSignature::BufferType(size.try_into().unwrap()).into() + TypeSignature::SequenceType(SequenceSubtype::BufferType(size.try_into().unwrap())).into() } @@ -717,7 +790,31 @@ mod tests { test_bad_expectation( Value::buff_from(vec![0,0xde,0xad,0xbe,0xef,0]).unwrap(), TypeSignature::from("(buff 2)")); - + } + + #[test] + fn test_string_ascii() { + test_deser_ser(Value::string_ascii_from_bytes(vec![61, 62, 63, 64]).unwrap()); + + // fail because we expect a shorter string + test_bad_expectation( + Value::string_ascii_from_bytes(vec![61, 62, 63, 64]).unwrap(), + TypeSignature::from("(string-ascii 3)")); + } + + #[test] + fn test_string_utf8() { + test_deser_ser(Value::string_utf8_from_bytes(vec![61, 62, 63, 64]).unwrap()); + test_deser_ser(Value::string_utf8_from_bytes(vec![61, 62, 63, 240, 159, 164, 151]).unwrap()); + + // fail because we expect a shorter string + test_bad_expectation( + Value::string_utf8_from_bytes(vec![61, 62, 63, 64]).unwrap(), + TypeSignature::from("(string-utf8 3)")); + + test_bad_expectation( + Value::string_utf8_from_bytes(vec![61, 62, 63, 240, 159, 164, 151]).unwrap(), + TypeSignature::from("(string-utf8 3)")); } #[test] diff --git a/src/vm/types/signatures.rs b/src/vm/types/signatures.rs index 8dbc4374d..a847edb23 100644 --- a/src/vm/types/signatures.rs +++ b/src/vm/types/signatures.rs @@ -6,7 +6,7 @@ use std::collections::{BTreeMap, HashMap}; use address::c32; use vm::costs::{cost_functions, CostOverflowingMath}; -use vm::types::{Value, MAX_VALUE_SIZE, MAX_TYPE_DEPTH, WRAPPER_VALUE_SIZE, +use vm::types::{Value, SequenceData, SequencedValue, CharType, MAX_VALUE_SIZE, MAX_TYPE_DEPTH, WRAPPER_VALUE_SIZE, QualifiedContractIdentifier, StandardPrincipalData, TraitIdentifier}; use vm::representations::{SymbolicExpression, SymbolicExpressionType, ClarityName, ContractName, TraitDefinition}; use vm::errors::{RuntimeErrorType, CheckErrors, IncomparableError, Error as VMError}; @@ -44,6 +44,9 @@ pub struct TupleTypeSignature { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BufferLength (u32); +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct StringUTF8Length (u32); + // INVARIANTS enforced by the Type Signatures. // 1. A TypeSignature constructor will always fail rather than construct a // type signature for a too large or invalid type. This is why any variable length @@ -57,32 +60,56 @@ pub enum TypeSignature { IntType, UIntType, BoolType, - BufferType(BufferLength), + SequenceType(SequenceSubtype), PrincipalType, - ListType(ListTypeData), TupleType(TupleTypeSignature), OptionalType(Box), ResponseType(Box<(TypeSignature, TypeSignature)>), TraitReferenceType(TraitIdentifier), } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SequenceSubtype { + BufferType(BufferLength), + ListType(ListTypeData), + StringType(StringSubtype), +} + +impl SequenceSubtype { + + pub fn unit_type(&self) -> TypeSignature { + match &self { + SequenceSubtype::ListType(ref list_data) => list_data.clone().destruct().0, + SequenceSubtype::BufferType(_) => TypeSignature::min_buffer(), + SequenceSubtype::StringType(StringSubtype::ASCII(_)) => TypeSignature::min_string_ascii(), + SequenceSubtype::StringType(StringSubtype::UTF8(_)) => TypeSignature::min_string_utf8(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum StringSubtype { + ASCII(BufferLength), + UTF8(StringUTF8Length), +} + use self::TypeSignature::{ NoType, IntType, UIntType, BoolType, - BufferType, + SequenceType, PrincipalType, - ListType, TupleType, OptionalType, ResponseType, TraitReferenceType }; -pub const BUFF_64: TypeSignature = BufferType(BufferLength(64)); -pub const BUFF_32: TypeSignature = BufferType(BufferLength(32)); -pub const BUFF_20: TypeSignature = BufferType(BufferLength(20)); +pub const BUFF_64: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(64))); +pub const BUFF_32: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(32))); +pub const BUFF_20: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(20))); +pub const BUFF_1: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(1))); #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ListTypeData { @@ -136,7 +163,7 @@ impl From for FunctionSignature { impl From for TypeSignature { fn from(data: ListTypeData) -> Self { - ListType(data) + SequenceType(SequenceSubtype::ListType(data)) } } @@ -185,12 +212,67 @@ impl TryFrom for BufferLength { fn try_from(data: i128) -> Result { if data > (MAX_VALUE_SIZE as i128) { Err(CheckErrors::ValueTooLarge) + } else if data < 0 { + Err(CheckErrors::ValueOutOfBounds) } else { Ok(BufferLength(data as u32)) } } } +impl From<&StringUTF8Length> for u32 { + fn from(v: &StringUTF8Length) -> u32 { + v.0 + } +} + +impl From for u32 { + fn from(v: StringUTF8Length) -> u32 { + v.0 + } +} + +impl TryFrom for StringUTF8Length { + type Error = CheckErrors; + fn try_from(data: u32) -> Result { + let len = data.checked_mul(4) + .ok_or_else(|| CheckErrors::ValueTooLarge)?; + if len > MAX_VALUE_SIZE { + Err(CheckErrors::ValueTooLarge) + } else { + Ok(StringUTF8Length(data)) + } + } +} + +impl TryFrom for StringUTF8Length { + type Error = CheckErrors; + fn try_from(data: usize) -> Result { + let len = data.checked_mul(4) + .ok_or_else(|| CheckErrors::ValueTooLarge)?; + if len > (MAX_VALUE_SIZE as usize) { + Err(CheckErrors::ValueTooLarge) + } else { + Ok(StringUTF8Length(data as u32)) + } + } +} + +impl TryFrom for StringUTF8Length { + type Error = CheckErrors; + fn try_from(data: i128) -> Result { + let len = data.checked_mul(4) + .ok_or_else(|| CheckErrors::ValueTooLarge)?; + if len > (MAX_VALUE_SIZE as i128) { + Err(CheckErrors::ValueTooLarge) + } else if data < 0 { + Err(CheckErrors::ValueOutOfBounds) + } else { + Ok(StringUTF8Length(data as u32)) + } + } +} + impl ListTypeData { pub fn new_list(entry_type: TypeSignature, max_len: u32) -> Result { let would_be_depth = 1 + entry_type.depth(); @@ -277,8 +359,8 @@ impl TypeSignature { pub fn admits_type(&self, other: &TypeSignature) -> bool { match self { - ListType(ref my_list_type) => { - if let ListType(other_list_type) = other { + SequenceType(SequenceSubtype::ListType(ref my_list_type)) => { + if let SequenceType(SequenceSubtype::ListType(other_list_type)) = other { if other_list_type.max_len <= 0 { // if other is an empty list, a list type should always admit. true @@ -291,6 +373,27 @@ impl TypeSignature { false } }, + SequenceType(SequenceSubtype::BufferType(ref my_len)) => { + if let SequenceType(SequenceSubtype::BufferType(ref other_len)) = other { + my_len.0 >= other_len.0 + } else { + false + } + }, + SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(len))) => { + if let SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(other_len))) = other { + len.0 >= other_len.0 + } else { + false + } + }, + SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(len))) => { + if let SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(other_len))) = other { + len.0 >= other_len.0 + } else { + false + } + }, OptionalType(ref my_inner_type) => { if let OptionalType(other_inner_type) = other { // Option types will always admit a "NoType" OptionalType -- which @@ -323,13 +426,6 @@ impl TypeSignature { false } }, - BufferType(ref my_len) => { - if let BufferType(ref other_len) = other { - my_len.0 >= other_len.0 - } else { - false - } - }, TupleType(ref tuple_sig) => { if let TupleType(ref other_tuple_sig) = other { tuple_sig.admits(other_tuple_sig) @@ -479,20 +575,28 @@ impl FunctionArg { impl TypeSignature { pub fn empty_buffer() -> TypeSignature { - BufferType(0_u32.try_into().unwrap()) + SequenceType(SequenceSubtype::BufferType(0_u32.try_into().unwrap())) } pub fn min_buffer() -> TypeSignature { - BufferType(1_u32.try_into().unwrap()) + SequenceType(SequenceSubtype::BufferType(1_u32.try_into().unwrap())) + } + + pub fn min_string_ascii() -> TypeSignature { + SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(1_u32.try_into().unwrap()))) + } + + pub fn min_string_utf8() -> TypeSignature { + SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(1_u32.try_into().unwrap()))) } pub fn max_buffer() -> TypeSignature { - BufferType(BufferLength(u32::try_from(MAX_VALUE_SIZE) - .expect("FAIL: Max Clarity Value Size is no longer realizable in Buffer Type"))) + SequenceType(SequenceSubtype::BufferType(BufferLength(u32::try_from(MAX_VALUE_SIZE) + .expect("FAIL: Max Clarity Value Size is no longer realizable in Buffer Type")))) } /// If one of the types is a NoType, return Ok(the other type), otherwise return least_supertype(a, b) - fn factor_out_no_type(a: &TypeSignature, b: &TypeSignature) -> Result { + pub fn factor_out_no_type(a: &TypeSignature, b: &TypeSignature) -> Result { if a.is_no_type() { Ok(b.clone()) } else if b.is_no_type() { @@ -539,7 +643,7 @@ impl TypeSignature { Ok(TupleTypeSignature::try_from(type_map_out).map(|x| x.into()) .expect("ERR: least_supertype attempted to construct a too-large supertype of two types")) }, - (ListType(ListTypeData{ max_len: len_a, entry_type: entry_a }), ListType(ListTypeData{ max_len: len_b, entry_type: entry_b })) => { + (SequenceType(SequenceSubtype::ListType(ListTypeData{ max_len: len_a, entry_type: entry_a })), SequenceType(SequenceSubtype::ListType(ListTypeData{ max_len: len_b, entry_type: entry_b }))) => { let entry_type = if *len_a == 0 { *(entry_b.clone()) @@ -561,13 +665,29 @@ impl TypeSignature { let some_type = Self::factor_out_no_type(some_a, some_b)?; Ok(Self::new_option(some_type)?) }, - (BufferType(buff_a), BufferType(buff_b)) => { + (SequenceType(SequenceSubtype::BufferType(buff_a)), SequenceType(SequenceSubtype::BufferType(buff_b))) => { let buff_len = if u32::from(buff_a) > u32::from(buff_b) { buff_a } else { buff_b }.clone(); - Ok(BufferType(buff_len)) + Ok(SequenceType(SequenceSubtype::BufferType(buff_len))) + }, + (SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(string_a))), SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(string_b)))) => { + let str_len = if u32::from(string_a) > u32::from(string_b) { + string_a + } else { + string_b + }.clone(); + Ok(SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(str_len)))) + }, + (SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(string_a))), SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(string_b)))) => { + let str_len = if u32::from(string_a) > u32::from(string_b) { + string_a + } else { + string_b + }.clone(); + Ok(SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(str_len)))) }, (NoType, x) | (x, NoType) => { Ok(x.clone()) @@ -599,14 +719,12 @@ impl TypeSignature { Value::Int(_v) => IntType, Value::UInt(_v) => UIntType, Value::Bool(_v) => BoolType, - Value::Buffer(buff_data) => { - let buff_length = BufferLength::try_from(buff_data.data.len()) - .expect("ERROR: Too large of a buffer successfully constructed."); - BufferType(buff_length) - }, Value::Tuple(v) => TupleType( v.type_signature.clone()), - Value::List(list_data) => ListType(list_data.type_signature.clone()), + Value::Sequence(SequenceData::List(list_data)) => list_data.type_signature(), + Value::Sequence(SequenceData::Buffer(buff_data)) => buff_data.type_signature(), + Value::Sequence(SequenceData::String(CharType::ASCII(ascii_data))) => ascii_data.type_signature(), + Value::Sequence(SequenceData::String(CharType::UTF8(utf8_data))) => utf8_data.type_signature(), Value::Optional(v) => v.type_signature(), Value::Response(v) => v.type_signature() } @@ -680,7 +798,35 @@ impl TypeSignature { } if let SymbolicExpressionType::LiteralValue(Value::Int(buff_len)) = &type_args[0].expr { BufferLength::try_from(*buff_len) - .map(|buff_len| TypeSignature::BufferType(buff_len)) + .map(|buff_len| SequenceType(SequenceSubtype::BufferType(buff_len))) + } else { + Err(CheckErrors::InvalidTypeDescription) + } + } + + // Parses type signatures of the form: + // (string-utf8 10) + fn parse_string_utf8_type_repr(type_args: &[SymbolicExpression]) -> Result { + if type_args.len() != 1 { + return Err(CheckErrors::InvalidTypeDescription) + } + if let SymbolicExpressionType::LiteralValue(Value::Int(utf8_len)) = &type_args[0].expr { + StringUTF8Length::try_from(*utf8_len) + .map(|utf8_len| SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(utf8_len)))) + } else { + Err(CheckErrors::InvalidTypeDescription) + } + } + + // Parses type signatures of the form: + // (string-ascii 10) + fn parse_string_ascii_type_repr(type_args: &[SymbolicExpression]) -> Result { + if type_args.len() != 1 { + return Err(CheckErrors::InvalidTypeDescription) + } + if let SymbolicExpressionType::LiteralValue(Value::Int(buff_len)) = &type_args[0].expr { + BufferLength::try_from(*buff_len) + .map(|buff_len| SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(buff_len)))) } else { Err(CheckErrors::InvalidTypeDescription) } @@ -719,6 +865,8 @@ impl TypeSignature { match compound_type.as_ref() { "list" => TypeSignature::parse_list_type_repr(rest, accounting), "buff" => TypeSignature::parse_buff_type_repr(rest), + "string-utf8" => TypeSignature::parse_string_utf8_type_repr(rest), + "string-ascii" => TypeSignature::parse_string_ascii_type_repr(rest), "tuple" => TypeSignature::parse_tuple_type_repr(rest, accounting), "optional" => TypeSignature::parse_optional_type_repr(rest, accounting), "response" => TypeSignature::parse_response_type_repr(rest, accounting), @@ -791,11 +939,11 @@ impl TypeSignature { match self { // NoType's may be asked for their size at runtime -- // legal constructions like `(ok 1)` have NoType parts (if they have unknown error variant types). - TraitReferenceType(_) | NoType | IntType | UIntType | BoolType | PrincipalType | BufferType(_) => 1, + TraitReferenceType(_) | NoType | IntType | UIntType | BoolType | PrincipalType | SequenceType(SequenceSubtype::BufferType(_)) | SequenceType(SequenceSubtype::StringType(_))=> 1, TupleType(tuple_sig) => { 1 + tuple_sig.max_depth() }, - ListType(list_type) => 1 + list_type.get_list_item_type().depth(), + SequenceType(SequenceSubtype::ListType(list_type)) => 1 + list_type.get_list_item_type().depth(), OptionalType(t) => 1 + t.depth(), ResponseType(v) => { 1 + cmp::max(v.0.depth(), v.1.depth()) @@ -817,9 +965,10 @@ impl TypeSignature { UIntType => Some(16), BoolType => Some(1), PrincipalType => Some(148), // 20+128 - BufferType(len) => Some(4 + u32::from(len)), TupleType(tuple_sig) => tuple_sig.inner_size(), - ListType(list_type) => list_type.inner_size(), + SequenceType(SequenceSubtype::BufferType(len)) | SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(len))) => Some(4 + u32::from(len)), + SequenceType(SequenceSubtype::ListType(list_type)) => list_type.inner_size(), + SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(len))) => Some(4 + 4 * u32::from(len)), OptionalType(t) => t.size().checked_add(WRAPPER_VALUE_SIZE), ResponseType(v) => { // ResponseTypes are 1 byte for the committed bool, @@ -847,9 +996,11 @@ impl TypeSignature { // These types all only use ~1 byte for their type enum NoType | IntType | UIntType | BoolType | PrincipalType => Some(1), // u32 length + type enum - BufferType(_) => Some(1 + 4), TupleType(tuple_sig) => tuple_sig.type_size(), - ListType(list_type) => list_type.type_size(), + SequenceType(SequenceSubtype::BufferType(_)) => Some(1 + 4), + SequenceType(SequenceSubtype::ListType(list_type)) => list_type.type_size(), + SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(_))) => Some(1 + 4), + SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(_))) => Some(1 + 4), OptionalType(t) => { t.inner_type_size()? .checked_add(1) @@ -1010,12 +1161,14 @@ impl fmt::Display for TypeSignature { IntType => write!(f, "int"), UIntType => write!(f, "uint"), BoolType => write!(f, "bool"), - BufferType(len) => write!(f, "(buff {})", len), OptionalType(t) => write!(f, "(optional {})", t), ResponseType(v) => write!(f, "(response {} {})", v.0, v.1), TupleType(t) => write!(f, "{}", t), PrincipalType => write!(f, "principal"), - ListType(list_type_data) => write!(f, "(list {} {})", list_type_data.max_len, list_type_data.entry_type), + SequenceType(SequenceSubtype::BufferType(len)) => write!(f, "(buff {})", len), + SequenceType(SequenceSubtype::ListType(list_type_data)) => write!(f, "(list {} {})", list_type_data.max_len, list_type_data.entry_type), + SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(len))) => write!(f, "(string-ascii {})", len), + SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(len))) => write!(f, "(string-utf8 {})", len), TraitReferenceType(trait_alias) => write!(f, "<{}>", trait_alias.to_string()), } } @@ -1027,6 +1180,12 @@ impl fmt::Display for BufferLength { } } +impl fmt::Display for StringUTF8Length { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + impl fmt::Display for FunctionArg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.signature) @@ -1049,7 +1208,7 @@ mod test { #[test] fn type_of_list_of_buffs() { let value = execute("(list \"abc\" \"abcde\")").unwrap().unwrap(); - let type_descr = "(list 2 (buff 5))".into(); + let type_descr = "(list 2 (string-ascii 5))".into(); assert_eq!(TypeSignature::type_of(&value), type_descr); } diff --git a/testnet/stacks-node/src/tests/bitcoin_regtest.rs b/testnet/stacks-node/src/tests/bitcoin_regtest.rs index 4cd7e8076..c5afc5164 100644 --- a/testnet/stacks-node/src/tests/bitcoin_regtest.rs +++ b/testnet/stacks-node/src/tests/bitcoin_regtest.rs @@ -296,14 +296,14 @@ fn bitcoind_integration_test() { 1 => { // On round 1, publish the KV contract // $ cat /tmp/out.clar - // (define-map store ((key (buff 32))) ((value (buff 32)))) - // (define-public (get-value (key (buff 32))) + // (define-map store ((key (string-ascii 32))) ((value (string-ascii 32)))) + // (define-public (get-value (key (string-ascii 32))) // (begin // (print (concat "Getting key " key)) // (match (map-get? store ((key key))) // entry (ok (get value entry)) // (err 0)))) - // (define-public (set-value (key (buff 32)) (value (buff 32))) + // (define-public (set-value (key (string-ascii 32)) (value (string-ascii 32))) // (begin // (print (concat "Setting key " key)) // (map-set store ((key key)) ((value value))) @@ -318,15 +318,15 @@ fn bitcoind_integration_test() { // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" let header_hash = chain_tip.block.block_hash(); let consensus_hash = chain_tip.metadata.consensus_hash; - let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000001000000000000000001007f9308b891b1593029c520cae33c25f55c4e720f875c85f8845e0ee7204047a0223f3587c033e0ddb7b0618183c56bf27a1521adf433d71f17d86a7b90c72973030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010200000003666f6f"; + let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000100c90ae0235365f3a73c595f8c6ab3c529807feb3cb269247329c9a24218d50d3f34c7eef5d28ba26831affa652a73ec32f098fec4bf1decd1ceb3fde4b8ce216b030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec()).unwrap(); }, 3 => { // On round 3, publish a "set:foo=bar" transaction - // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" + // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 2 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" let header_hash = chain_tip.block.block_hash(); let consensus_hash = chain_tip.metadata.consensus_hash; - let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe400000000000000020000000000000000010132033d83ad5051a52cef15cb88a93ac046e91a7ea2c6bf2110efdf8827ad8e0c6d0fbce1087637647ecf771c16613637742c08a4422cddfe7af03227257061ad030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020200000003666f6f0200000003626172"; + let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe400000000000000020000000000000000010076df7ad6ddf5cf3d2eb5b96bed15c95bdb975470add5bedeee0b6f00e884c0213b6718ffd75fbb98783168bca19559798ac44647b330e481b19d3eba1b2248c6030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020d00000003666f6f0d00000003626172"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(set_foo_bar).unwrap().to_vec()).unwrap(); }, 4 => { @@ -334,7 +334,7 @@ fn bitcoind_integration_test() { // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 3 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" let header_hash = chain_tip.block.block_hash(); let consensus_hash = chain_tip.metadata.consensus_hash; - let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000300000000000000000100f1ffc472083f4fea947a6d1a83d0ddf0353dc0e9fac94d74da9d668b61676d1966474bc890f94c5fdb4d6ef816682f9073a2185e6ca8f8a6aa25a36ed851399d030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010200000003666f6f"; + let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000300000000000000000101fd27e1727f78c38620dc155ca9940a02e964d08fcd35ac4fc8fbc56d62caac585891f537751626dc87fc7f212b3e7586845d36800e742c3f2b0c0a05cf81435e030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec()).unwrap(); }, 5 => { diff --git a/testnet/stacks-node/src/tests/mod.rs b/testnet/stacks-node/src/tests/mod.rs index 4ff21790c..aedbf5496 100644 --- a/testnet/stacks-node/src/tests/mod.rs +++ b/testnet/stacks-node/src/tests/mod.rs @@ -25,14 +25,14 @@ use super::node::{TESTNET_CHAIN_ID}; use super::burnchains::bitcoin_regtest_controller::ParsedUTXO; // $ cat /tmp/out.clar -pub const STORE_CONTRACT: &str = r#"(define-map store ((key (buff 32))) ((value (buff 32)))) - (define-public (get-value (key (buff 32))) +pub const STORE_CONTRACT: &str = r#"(define-map store ((key (string-ascii 32))) ((value (string-ascii 32)))) + (define-public (get-value (key (string-ascii 32))) (begin (print (concat "Getting key " key)) (match (map-get? store { key: key }) entry (ok (get value entry)) (err 0)))) - (define-public (set-value (key (buff 32)) (value (buff 32))) + (define-public (set-value (key (string-ascii 32)) (value (string-ascii 32))) (begin (print (concat "Setting key " key)) (map-set store { key: key } { value: value }) @@ -215,19 +215,19 @@ fn should_succeed_mining_valid_txs() { 2 => { // On round 2, publish a "get:foo" transaction // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000001000000000000000001007f9308b891b1593029c520cae33c25f55c4e720f875c85f8845e0ee7204047a0223f3587c033e0ddb7b0618183c56bf27a1521adf433d71f17d86a7b90c72973030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010200000003666f6f"; + let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000100c90ae0235365f3a73c595f8c6ab3c529807feb3cb269247329c9a24218d50d3f34c7eef5d28ba26831affa652a73ec32f098fec4bf1decd1ceb3fde4b8ce216b030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec()).unwrap(); }, 3 => { // On round 3, publish a "set:foo=bar" transaction // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 2 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" - let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe400000000000000020000000000000000010132033d83ad5051a52cef15cb88a93ac046e91a7ea2c6bf2110efdf8827ad8e0c6d0fbce1087637647ecf771c16613637742c08a4422cddfe7af03227257061ad030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020200000003666f6f0200000003626172"; + let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe400000000000000020000000000000000010076df7ad6ddf5cf3d2eb5b96bed15c95bdb975470add5bedeee0b6f00e884c0213b6718ffd75fbb98783168bca19559798ac44647b330e481b19d3eba1b2248c6030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020d00000003666f6f0d00000003626172"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(set_foo_bar).unwrap().to_vec()).unwrap(); }, 4 => { // On round 4, publish a "get:foo" transaction // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 3 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000300000000000000000100f1ffc472083f4fea947a6d1a83d0ddf0353dc0e9fac94d74da9d668b61676d1966474bc890f94c5fdb4d6ef816682f9073a2185e6ca8f8a6aa25a36ed851399d030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010200000003666f6f"; + let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000300000000000000000101fd27e1727f78c38620dc155ca9940a02e964d08fcd35ac4fc8fbc56d62caac585891f537751626dc87fc7f212b3e7586845d36800e742c3f2b0c0a05cf81435e030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec()).unwrap(); }, 5 => { @@ -343,7 +343,7 @@ fn should_succeed_mining_valid_txs() { StacksTransactionEvent::SmartContractEvent(data) => { format!("{}", data.key.0) == "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.store" && data.key.1 == "print" && - format!("{}", data.value) == "0x53657474696e67206b657920666f6f" // "Setting key foo" in hexa + format!("{}", data.value) == "\"Setting key foo\"".to_string() }, _ => false }); @@ -379,7 +379,7 @@ fn should_succeed_mining_valid_txs() { StacksTransactionEvent::SmartContractEvent(data) => { format!("{}", data.key.0) == "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.store" && data.key.1 == "print" && - format!("{}", data.value) == "0x47657474696e67206b657920666f6f" // "Getting key foo" in hexa + format!("{}", data.value) == "\"Getting key foo\"".to_string() }, _ => false }); @@ -451,20 +451,20 @@ fn should_succeed_handling_malformed_and_valid_txs() { // On round 2, publish a "get:foo" transaction (mainnet instead of testnet). // Will not be mined // ./blockstack-cli contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "0000000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000100cbb46766a2bc03261f6bd428fdd6ce63da8ed04713e6476426390ccc15d2b1c133d9ba30a47b51cd467a09a25f3d7fa2bb4b85379f7d0601df02268cb623e231030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010200000003666f6f"; + let get_foo = "0000000001040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000101f5c408658708fca3aa625525b0d2314519af487f53e9a552eab6aeb01577dc5d6786eff505b5b781ed7b512bcbde871ab4d438394220080ca01a2a8c46b11361030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec()).unwrap(); }, 3 => { // On round 3, publish a "set:foo=bar" transaction (chain-id not matching). // Will not be mined // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store set-value -e \"foo\" -e \"bar\" - let set_foo_bar = "8000000001040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000101e57846af212a3e9536c86446d3f39210f6edd691f5c6db65feea3e188822dc2c09e8f82b2f7449d54b58e1a6666b003f65c104f3f9b41a34211560b8ce2c1095030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020200000003666f6f0200000003626172"; + let set_foo_bar = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe40000000000000001000000000000000001017112764d8a0c0a5476fc6ec37de6bc564259c6ccd4ef8ce06c1cd23f58c66a114485df6bbdf147ded8ae4fc6dda87686052bc9aa4734265c3ae4b64613b2ceb1030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265097365742d76616c7565000000020d00000003666f6f0d00000003626172"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(set_foo_bar).unwrap().to_vec()).unwrap(); }, 4 => { // On round 4, publish a "get:foo" transaction // ./blockstack-cli --testnet contract-call 043ff5004e3d695060fa48ac94c96049b8c14ef441c50a184a6a3875d2a000f3 0 1 STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A store get-value -e \"foo\" - let get_foo = "8000000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000100e11fa0938e579c868137cfdd95fc0d6107a32c7a8864bbff2852c792c1759a38314e42922702b709c7b17c93d406f9d8057fb7c14736e5d85ff24acf89e921d6030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010200000003666f6f"; + let get_foo = "8080000000040021a3c334fc0ee50359353799e8b2605ac6be1fe4000000000000000100000000000000000100c90ae0235365f3a73c595f8c6ab3c529807feb3cb269247329c9a24218d50d3f34c7eef5d28ba26831affa652a73ec32f098fec4bf1decd1ceb3fde4b8ce216b030200000000021a21a3c334fc0ee50359353799e8b2605ac6be1fe40573746f7265096765742d76616c7565000000010d00000003666f6f"; tenure.mem_pool.submit_raw(&consensus_hash, &header_hash,hex_bytes(get_foo).unwrap().to_vec()).unwrap(); }, _ => {}