Skip to content

Commit d4d08b8

Browse files
committed
Merge branch 'develop' of https://github.com/stacks-network/stacks-core into chore/merge-develop-to-aac-client-breaking
2 parents c215d2b + 1c7bf30 commit d4d08b8

File tree

16 files changed

+466
-309
lines changed

16 files changed

+466
-309
lines changed

clarity-types/src/errors/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub struct IncomparableError<T> {
4444

4545
/// Errors that can occur during the runtime execution of Clarity contracts in the virtual machine.
4646
/// These encompass type-checking failures, interpreter issues, runtime errors, and premature returns.
47-
/// Unlike static analysis errors in `ClarityError::StaticCheck(CheckError)` or `ClarityError::Parse(ParseError)`,
47+
/// Unlike static analysis errors in `ClarityError::StaticCheck(StaticCheckError)` or `ClarityError::Parse(ParseError)`,
4848
/// which are caught before execution during type-checking or parsing, these errors occur during dynamic
4949
/// evaluation and may involve conditions not detectable statically, such as dynamically constructed expressions
5050
/// (e.g., based on VRF seeds or runtime data).

clarity-types/src/types/signatures.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,18 @@ impl TypeSignature {
875875
/// Longest ([`MAX_TO_ASCII_RESULT_LEN`]) string allowed for `to-ascii?` call.
876876
pub const TO_ASCII_STRING_ASCII_MAX: TypeSignature =
877877
Self::type_ascii_const(MAX_TO_ASCII_RESULT_LEN);
878+
/// Longest string result possible for `(to-ascii? <int>)` result
879+
/// e.g. "-170141183460469231731687303715884105728"
880+
pub const TO_ASCII_INT_RESULT_MAX: TypeSignature = Self::type_ascii_const(40);
881+
/// Longest string result possible for `(to-ascii? <uint>)` result
882+
/// e.g. "u340282366920938463463374607431768211455"
883+
pub const TO_ASCII_UINT_RESULT_MAX: TypeSignature = Self::type_ascii_const(40);
884+
/// Longest string result possible for `(to-ascii? <bool>)` result
885+
/// e.g. "false"
886+
pub const TO_ASCII_BOOL_RESULT_MAX: TypeSignature = Self::type_ascii_const(5);
887+
/// Longest string result possible for `(to-ascii? <principal>)` result
888+
/// e.g. "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.contract-name-can-be-up-to-128-characters-long-so-41-characters-for-the-address-plus-1-for-the-dot-plus-128-for-the-name-is-170-"
889+
pub const TO_ASCII_PRINCIPAL_RESULT_MAX: TypeSignature = Self::type_ascii_const(170);
878890

879891
/// Longest ([`CONTRACT_MAX_NAME_LENGTH`]) string allowed for `contract-name`.
880892
pub const CONTRACT_NAME_STRING_ASCII_MAX: TypeSignature =
@@ -920,6 +932,14 @@ impl TypeSignature {
920932
Self::type_ascii_const(len)
921933
}
922934

935+
/// Creates a string ASCII type with the specified length.
936+
/// Returns an error if the provided length is invalid.
937+
pub fn new_ascii_type(len: i128) -> Result<Self, CheckErrorKind> {
938+
Ok(SequenceType(SequenceSubtype::StringType(
939+
StringSubtype::ASCII(BufferLength::try_from_i128(len)?),
940+
)))
941+
}
942+
923943
/// If one of the types is a NoType, return Ok(the other type), otherwise return least_supertype(a, b)
924944
pub fn factor_out_no_type(
925945
epoch: &StacksEpochId,

clarity/src/vm/analysis/type_checker/v2_1/natives/conversions.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use clarity_types::errors::CheckErrorKind;
2+
use clarity_types::types::{StringSubtype, MAX_TO_ASCII_BUFFER_LEN};
13
use stacks_common::types::StacksEpochId;
24

35
use super::TypeChecker;
@@ -41,3 +43,61 @@ pub fn check_special_from_consensus_buff(
4143
checker.type_check_expects(&args[1], context, &TypeSignature::BUFFER_MAX)?;
4244
TypeSignature::new_option(result_type).map_err(StaticCheckError::from)
4345
}
46+
47+
/// `to-ascii?` admits exactly one argument, a value to convert to a
48+
/// `string-ascii`. It can be any of the following types:
49+
/// - `int`
50+
/// - `uint`
51+
/// - `bool`
52+
/// - `principal`
53+
/// - `(buff 524284)`
54+
/// - `(string-utf8 262144)`
55+
///
56+
/// It returns a `(response (string-ascii 1048571) uint)`.
57+
pub fn check_special_to_ascii(
58+
checker: &mut TypeChecker,
59+
args: &[SymbolicExpression],
60+
context: &TypingContext,
61+
) -> Result<TypeSignature, StaticCheckError> {
62+
check_argument_count(1, args)?;
63+
let input_type = checker.type_check(
64+
args.first()
65+
.ok_or(CheckErrorKind::CheckerImplementationFailure)?,
66+
context,
67+
)?;
68+
69+
let result_type = match input_type {
70+
TypeSignature::IntType => TypeSignature::TO_ASCII_INT_RESULT_MAX,
71+
TypeSignature::UIntType => TypeSignature::TO_ASCII_UINT_RESULT_MAX,
72+
TypeSignature::BoolType => TypeSignature::TO_ASCII_BOOL_RESULT_MAX,
73+
TypeSignature::PrincipalType | TypeSignature::CallableType(_) => {
74+
TypeSignature::TO_ASCII_PRINCIPAL_RESULT_MAX
75+
}
76+
TypeSignature::SequenceType(SequenceSubtype::BufferType(len))
77+
if u32::from(len.clone()) <= MAX_TO_ASCII_BUFFER_LEN =>
78+
{
79+
// Each byte in the buffer becomes two ASCII characters, plus "0x" prefix
80+
TypeSignature::new_ascii_type((u32::from(len) * 2 + 2).into())?
81+
}
82+
TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8(len))) => {
83+
// Each UTF-8 character is exactly one ASCII character
84+
TypeSignature::new_ascii_type(u32::from(len).into())?
85+
}
86+
_ => {
87+
let types = vec![
88+
TypeSignature::IntType,
89+
TypeSignature::UIntType,
90+
TypeSignature::BoolType,
91+
TypeSignature::PrincipalType,
92+
TypeSignature::TO_ASCII_BUFFER_MAX,
93+
TypeSignature::STRING_UTF8_MAX,
94+
];
95+
return Err(CheckErrorKind::UnionTypeError(types, input_type.into()).into());
96+
}
97+
};
98+
Ok(
99+
TypeSignature::new_response(result_type, TypeSignature::UIntType).map_err(|_| {
100+
CheckErrorKind::Expects("FATAL: Legal Clarity response type marked invalid".into())
101+
})?,
102+
)
103+
}

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

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,25 +1207,7 @@ impl TypedNativeFunction {
12071207
)
12081208
.map_err(|_| CheckErrorKind::Expects("Bad constructor".into()))?,
12091209
}))),
1210-
ToAscii => Simple(SimpleNativeFunction(FunctionType::UnionArgs(
1211-
vec![
1212-
TypeSignature::IntType,
1213-
TypeSignature::UIntType,
1214-
TypeSignature::BoolType,
1215-
TypeSignature::PrincipalType,
1216-
TypeSignature::TO_ASCII_BUFFER_MAX,
1217-
TypeSignature::STRING_UTF8_MAX,
1218-
],
1219-
TypeSignature::new_response(
1220-
TypeSignature::TO_ASCII_STRING_ASCII_MAX,
1221-
TypeSignature::UIntType,
1222-
)
1223-
.map_err(|_| {
1224-
CheckErrorKind::Expects(
1225-
"FATAL: Legal Clarity response type marked invalid".into(),
1226-
)
1227-
})?,
1228-
))),
1210+
ToAscii => Special(SpecialNativeFunction(&conversions::check_special_to_ascii)),
12291211
RestrictAssets => Special(SpecialNativeFunction(
12301212
&post_conditions::check_restrict_assets,
12311213
)),

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

Lines changed: 0 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -3511,142 +3511,3 @@ fn test_contract_hash(#[case] version: ClarityVersion, #[case] epoch: StacksEpoc
35113511
assert_eq!(&actual, expected, "Failed for test case: {description}");
35123512
}
35133513
}
3514-
3515-
/// Pass various types to `to-ascii?`
3516-
#[apply(test_clarity_versions)]
3517-
fn test_to_ascii(#[case] version: ClarityVersion, #[case] epoch: StacksEpochId) {
3518-
let to_ascii_response_type = Some(
3519-
TypeSignature::new_response(
3520-
TypeSignature::TO_ASCII_STRING_ASCII_MAX,
3521-
TypeSignature::UIntType,
3522-
)
3523-
.unwrap(),
3524-
);
3525-
let to_ascii_expected_types = vec![
3526-
TypeSignature::IntType,
3527-
TypeSignature::UIntType,
3528-
TypeSignature::BoolType,
3529-
TypeSignature::PrincipalType,
3530-
TypeSignature::TO_ASCII_BUFFER_MAX,
3531-
TypeSignature::STRING_UTF8_MAX,
3532-
];
3533-
let test_cases = [
3534-
(
3535-
"(to-ascii? 123)",
3536-
"int type",
3537-
Ok(to_ascii_response_type.clone()),
3538-
),
3539-
(
3540-
"(to-ascii? u123)",
3541-
"uint type",
3542-
Ok(to_ascii_response_type.clone()),
3543-
),
3544-
(
3545-
"(to-ascii? true)",
3546-
"bool type",
3547-
Ok(to_ascii_response_type.clone()),
3548-
),
3549-
(
3550-
"(to-ascii? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM)",
3551-
"standard principal",
3552-
Ok(to_ascii_response_type.clone()),
3553-
),
3554-
(
3555-
"(to-ascii? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.foo)",
3556-
"contract principal",
3557-
Ok(to_ascii_response_type.clone()),
3558-
),
3559-
(
3560-
"(to-ascii? 0x1234)",
3561-
"buffer type",
3562-
Ok(to_ascii_response_type.clone()),
3563-
),
3564-
(
3565-
&format!("(to-ascii? 0x{})", "ff".repeat(524284)),
3566-
"max len buffer type",
3567-
Ok(to_ascii_response_type.clone()),
3568-
),
3569-
(
3570-
&format!("(to-ascii? 0x{})", "ff".repeat(524285)),
3571-
"oversized buffer type",
3572-
Err(CheckErrorKind::UnionTypeError(
3573-
to_ascii_expected_types.clone(),
3574-
Box::new(TypeSignature::SequenceType(SequenceSubtype::BufferType(
3575-
BufferLength::try_from(524285u32).unwrap(),
3576-
))),
3577-
)),
3578-
),
3579-
(
3580-
"(to-ascii? u\"I am serious, and don't call me Shirley.\")",
3581-
"utf8 string",
3582-
Ok(to_ascii_response_type),
3583-
),
3584-
(
3585-
"(to-ascii? \"60 percent of the time, it works every time\")",
3586-
"ascii string",
3587-
Err(CheckErrorKind::UnionTypeError(
3588-
to_ascii_expected_types.clone(),
3589-
Box::new(TypeSignature::SequenceType(SequenceSubtype::StringType(
3590-
StringSubtype::ASCII(BufferLength::try_from(43u32).unwrap()),
3591-
))),
3592-
)),
3593-
),
3594-
(
3595-
"(to-ascii? (list 1 2 3))",
3596-
"list type",
3597-
Err(CheckErrorKind::UnionTypeError(
3598-
to_ascii_expected_types.clone(),
3599-
Box::new(TypeSignature::SequenceType(SequenceSubtype::ListType(
3600-
ListTypeData::new_list(TypeSignature::IntType, 3).unwrap(),
3601-
))),
3602-
)),
3603-
),
3604-
(
3605-
"(to-ascii? { a: 1, b: u2 })",
3606-
"tuple type",
3607-
Err(CheckErrorKind::UnionTypeError(
3608-
to_ascii_expected_types.clone(),
3609-
Box::new(TypeSignature::TupleType(
3610-
vec![
3611-
(ClarityName::from("a"), TypeSignature::IntType),
3612-
(ClarityName::from("b"), TypeSignature::UIntType),
3613-
]
3614-
.try_into()
3615-
.unwrap(),
3616-
)),
3617-
)),
3618-
),
3619-
(
3620-
"(to-ascii? (some u789))",
3621-
"optional type",
3622-
Err(CheckErrorKind::UnionTypeError(
3623-
to_ascii_expected_types.clone(),
3624-
Box::new(TypeSignature::new_option(TypeSignature::UIntType).unwrap()),
3625-
)),
3626-
),
3627-
(
3628-
"(to-ascii? (ok true))",
3629-
"response type",
3630-
Err(CheckErrorKind::UnionTypeError(
3631-
to_ascii_expected_types.clone(),
3632-
Box::new(
3633-
TypeSignature::new_response(TypeSignature::BoolType, TypeSignature::NoType)
3634-
.unwrap(),
3635-
),
3636-
)),
3637-
),
3638-
];
3639-
3640-
for (source, description, clarity4_expected) in test_cases.iter() {
3641-
let result = mem_run_analysis(source, version, epoch);
3642-
let actual = result.map(|(type_sig, _)| type_sig).map_err(|e| *e.err);
3643-
3644-
let expected = if version >= ClarityVersion::Clarity4 {
3645-
clarity4_expected
3646-
} else {
3647-
&Err(CheckErrorKind::UnknownFunction("to-ascii?".to_string()))
3648-
};
3649-
3650-
assert_eq!(&actual, expected, "Failed for test case: {description}");
3651-
}
3652-
}

0 commit comments

Comments
 (0)