Skip to content

Commit 4747f97

Browse files
authored
Merge pull request #6436 from obycode/feat/to-ascii
[Clarity-4] add `to-ascii?`
2 parents c1a989f + eb588c2 commit 4747f97

File tree

21 files changed

+562
-48
lines changed

21 files changed

+562
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1616
- Creates epoch 3.3 and costs-4 in preparation for a hardfork to activate Clarity 4
1717
- Adds support for new Clarity 4 builtins (not activated until epoch 3.3):
1818
- `contract-hash?`
19+
- `to-ascii?`
1920
- Added `contract_cost_limit_percentage` to the miner config file — sets the percentage of a block’s execution cost at which, if a large non-boot contract call would cause a BlockTooBigError, the miner will stop adding further non-boot contract calls and only include STX transfers and boot contract calls for the remainder of the block.
2021

2122
### Changed

clarity-types/src/types/signatures.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ use self::TypeSignature::{
185185
ResponseType, SequenceType, TraitReferenceType, TupleType, UIntType,
186186
};
187187

188+
/// Maximum string length returned from `to-ascii?`.
189+
/// 5 bytes reserved for embedding in response.
190+
const MAX_TO_ASCII_RESULT_LEN: u32 = MAX_VALUE_SIZE - 5;
191+
192+
/// Maximum buffer length returned from `to-ascii?`.
193+
/// 2 bytes reserved for "0x" prefix and 2 characters per byte.
194+
pub const MAX_TO_ASCII_BUFFER_LEN: u32 = (MAX_TO_ASCII_RESULT_LEN - 2) / 2;
195+
188196
lazy_static! {
189197
pub static ref BUFF_64: TypeSignature = {
190198
#[allow(clippy::expect_used)]
@@ -234,6 +242,22 @@ lazy_static! {
234242
BufferLength::try_from(16u32).expect("BUG: Legal Clarity buffer length marked invalid"),
235243
))
236244
};
245+
/// Maximum-sized buffer allowed for `to-ascii?` call.
246+
pub static ref TO_ASCII_MAX_BUFF: TypeSignature = {
247+
#[allow(clippy::expect_used)]
248+
SequenceType(SequenceSubtype::BufferType(
249+
BufferLength::try_from(MAX_TO_ASCII_BUFFER_LEN)
250+
.expect("BUG: Legal Clarity buffer length marked invalid"),
251+
))
252+
};
253+
/// Maximum-length string returned from `to-ascii?`
254+
pub static ref TO_ASCII_RESPONSE_STRING: TypeSignature = {
255+
#[allow(clippy::expect_used)]
256+
SequenceType(SequenceSubtype::StringType(
257+
StringSubtype::ASCII(BufferLength::try_from(MAX_TO_ASCII_RESULT_LEN)
258+
.expect("BUG: Legal Clarity buffer length marked invalid")),
259+
))
260+
};
237261
}
238262

239263
pub const ASCII_40: TypeSignature = SequenceType(SequenceSubtype::StringType(

clarity/src/vm/analysis/arithmetic_checker/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ impl ArithmeticOnlyChecker<'_> {
185185
IsStandard | PrincipalDestruct | PrincipalConstruct => {
186186
Err(Error::FunctionNotPermitted(function))
187187
}
188-
IntToAscii | IntToUtf8 | StringToInt | StringToUInt => {
188+
IntToAscii | IntToUtf8 | StringToInt | StringToUInt | ToAscii => {
189189
Err(Error::FunctionNotPermitted(function))
190190
}
191191
Sha512 | Sha512Trunc256 | Secp256k1Recover | Secp256k1Verify | Hash160 | Sha256

clarity/src/vm/analysis/read_only_checker/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ impl<'a, 'b> ReadOnlyChecker<'a, 'b> {
293293
| AsContract | Begin | FetchVar | GetStxBalance | StxGetAccount | GetTokenBalance
294294
| GetAssetOwner | GetTokenSupply | ElementAt | IndexOf | Slice | ReplaceAt
295295
| BitwiseAnd | BitwiseOr | BitwiseNot | BitwiseLShift | BitwiseRShift | BitwiseXor2
296-
| ElementAtAlias | IndexOfAlias | ContractHash => {
296+
| ElementAtAlias | IndexOfAlias | ContractHash | ToAscii => {
297297
// Check all arguments.
298298
self.check_each_expression_is_read_only(args)
299299
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,7 @@ impl TypedNativeFunction {
778778
| StringToUInt | IntToAscii | IntToUtf8 | GetBurnBlockInfo | StxTransferMemo
779779
| StxGetAccount | BitwiseAnd | BitwiseOr | BitwiseNot | BitwiseLShift
780780
| BitwiseRShift | BitwiseXor2 | Slice | ToConsensusBuff | FromConsensusBuff
781-
| ReplaceAt | GetStacksBlockInfo | GetTenureInfo | ContractHash => {
781+
| ReplaceAt | GetStacksBlockInfo | GetTenureInfo | ContractHash | ToAscii => {
782782
return Err(CheckErrors::Expects(
783783
"Clarity 2+ keywords should not show up in 2.05".into(),
784784
));

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::vm::diagnostic::DiagnosableError;
2727
use crate::vm::functions::{handle_binding_list, NativeFunctions};
2828
use crate::vm::types::signatures::{
2929
CallableSubtype, FunctionArgSignature, FunctionReturnsSignature, SequenceSubtype, ASCII_40,
30-
UTF8_40,
30+
TO_ASCII_MAX_BUFF, TO_ASCII_RESPONSE_STRING, UTF8_40,
3131
};
3232
use crate::vm::types::{
3333
BlockInfoProperty, BufferLength, BurnBlockInfoProperty, FixedFunction, FunctionArg,
@@ -1183,6 +1183,23 @@ impl TypedNativeFunction {
11831183
returns: TypeSignature::new_response(BUFF_32.clone(), TypeSignature::UIntType)
11841184
.map_err(|_| CheckErrors::Expects("Bad constructor".into()))?,
11851185
}))),
1186+
ToAscii => Simple(SimpleNativeFunction(FunctionType::UnionArgs(
1187+
vec![
1188+
TypeSignature::IntType,
1189+
TypeSignature::UIntType,
1190+
TypeSignature::BoolType,
1191+
TypeSignature::PrincipalType,
1192+
TO_ASCII_MAX_BUFF.clone(),
1193+
TypeSignature::max_string_utf8()?,
1194+
],
1195+
TypeSignature::new_response(
1196+
TO_ASCII_RESPONSE_STRING.clone(),
1197+
TypeSignature::UIntType,
1198+
)
1199+
.map_err(|_| {
1200+
CheckErrors::Expects("FATAL: Legal Clarity response type marked invalid".into())
1201+
})?,
1202+
))),
11861203
};
11871204

11881205
Ok(out)

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

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::vm::ast::parse;
3131
use crate::vm::costs::LimitedCostTracker;
3232
use crate::vm::database::MemoryBackingStore;
3333
use crate::vm::tests::test_clarity_versions;
34-
use crate::vm::types::signatures::CallableSubtype;
34+
use crate::vm::types::signatures::{CallableSubtype, TO_ASCII_MAX_BUFF, TO_ASCII_RESPONSE_STRING};
3535
use crate::vm::types::{
3636
BufferLength, ListTypeData, QualifiedContractIdentifier, SequenceSubtype, StringSubtype,
3737
StringUTF8Length, TypeSignature,
@@ -3678,3 +3678,132 @@ fn test_contract_hash(#[case] version: ClarityVersion, #[case] epoch: StacksEpoc
36783678
assert_eq!(&actual, expected, "Failed for test case: {description}");
36793679
}
36803680
}
3681+
3682+
/// Pass various types to `to-ascii?`
3683+
#[apply(test_clarity_versions)]
3684+
fn test_to_ascii(#[case] version: ClarityVersion, #[case] epoch: StacksEpochId) {
3685+
let to_ascii_response_type = Some(
3686+
TypeSignature::new_response(TO_ASCII_RESPONSE_STRING.clone(), TypeSignature::UIntType)
3687+
.unwrap(),
3688+
);
3689+
let to_ascii_expected_types = vec![
3690+
TypeSignature::IntType,
3691+
TypeSignature::UIntType,
3692+
TypeSignature::BoolType,
3693+
TypeSignature::PrincipalType,
3694+
TO_ASCII_MAX_BUFF.clone(),
3695+
TypeSignature::max_string_utf8().unwrap(),
3696+
];
3697+
let test_cases = [
3698+
(
3699+
"(to-ascii? 123)",
3700+
"int type",
3701+
Ok(to_ascii_response_type.clone()),
3702+
),
3703+
(
3704+
"(to-ascii? u123)",
3705+
"uint type",
3706+
Ok(to_ascii_response_type.clone()),
3707+
),
3708+
(
3709+
"(to-ascii? true)",
3710+
"bool type",
3711+
Ok(to_ascii_response_type.clone()),
3712+
),
3713+
(
3714+
"(to-ascii? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM)",
3715+
"standard principal",
3716+
Ok(to_ascii_response_type.clone()),
3717+
),
3718+
(
3719+
"(to-ascii? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.foo)",
3720+
"contract principal",
3721+
Ok(to_ascii_response_type.clone()),
3722+
),
3723+
(
3724+
"(to-ascii? 0x1234)",
3725+
"buffer type",
3726+
Ok(to_ascii_response_type.clone()),
3727+
),
3728+
(
3729+
&format!("(to-ascii? 0x{})", "ff".repeat(524285)),
3730+
"oversized buffer type",
3731+
Err(CheckErrors::UnionTypeError(
3732+
to_ascii_expected_types.clone(),
3733+
TypeSignature::SequenceType(SequenceSubtype::BufferType(
3734+
BufferLength::try_from(524285u32).unwrap(),
3735+
)),
3736+
)),
3737+
),
3738+
(
3739+
"(to-ascii? u\"I am serious, and don't call me Shirley.\")",
3740+
"utf8 string",
3741+
Ok(to_ascii_response_type),
3742+
),
3743+
(
3744+
"(to-ascii? \"60 percent of the time, it works every time\")",
3745+
"ascii string",
3746+
Err(CheckErrors::UnionTypeError(
3747+
to_ascii_expected_types.clone(),
3748+
TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII(
3749+
BufferLength::try_from(43u32).unwrap(),
3750+
))),
3751+
)),
3752+
),
3753+
(
3754+
"(to-ascii? (list 1 2 3))",
3755+
"list type",
3756+
Err(CheckErrors::UnionTypeError(
3757+
to_ascii_expected_types.clone(),
3758+
TypeSignature::SequenceType(SequenceSubtype::ListType(
3759+
ListTypeData::new_list(TypeSignature::IntType, 3).unwrap(),
3760+
)),
3761+
)),
3762+
),
3763+
(
3764+
"(to-ascii? { a: 1, b: u2 })",
3765+
"tuple type",
3766+
Err(CheckErrors::UnionTypeError(
3767+
to_ascii_expected_types.clone(),
3768+
TypeSignature::TupleType(
3769+
vec![
3770+
(ClarityName::from("a"), TypeSignature::IntType),
3771+
(ClarityName::from("b"), TypeSignature::UIntType),
3772+
]
3773+
.try_into()
3774+
.unwrap(),
3775+
),
3776+
)),
3777+
),
3778+
(
3779+
"(to-ascii? (some u789))",
3780+
"optional type",
3781+
Err(CheckErrors::UnionTypeError(
3782+
to_ascii_expected_types.clone(),
3783+
TypeSignature::new_option(TypeSignature::UIntType).unwrap(),
3784+
)),
3785+
),
3786+
(
3787+
"(to-ascii? (ok true))",
3788+
"response type",
3789+
Err(CheckErrors::UnionTypeError(
3790+
to_ascii_expected_types.clone(),
3791+
TypeSignature::new_response(TypeSignature::BoolType, TypeSignature::NoType)
3792+
.unwrap(),
3793+
)),
3794+
),
3795+
];
3796+
3797+
for (source, description, clarity4_expected) in test_cases.iter() {
3798+
let result = mem_run_analysis(source, version, epoch);
3799+
let actual = result.map(|(type_sig, _)| type_sig).map_err(|e| e.err);
3800+
3801+
let expected = if version >= ClarityVersion::Clarity4 {
3802+
clarity4_expected
3803+
} else {
3804+
&Err(CheckErrors::UnknownFunction("to-ascii?".to_string()))
3805+
};
3806+
3807+
assert_eq!(&actual, expected, "Failed for test case: {description}");
3808+
}
3809+
}

clarity/src/vm/costs/cost_functions.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ define_named_enum!(ClarityCostFunction {
158158
BitwiseLShift("cost_bitwise_left_shift"),
159159
BitwiseRShift("cost_bitwise_right_shift"),
160160
ContractHash("cost_contract_hash"),
161+
ToAscii("cost_to_ascii"),
161162
Unimplemented("cost_unimplemented"),
162163
});
163164

@@ -328,6 +329,7 @@ pub trait CostValues {
328329
fn cost_bitwise_left_shift(n: u64) -> InterpreterResult<ExecutionCost>;
329330
fn cost_bitwise_right_shift(n: u64) -> InterpreterResult<ExecutionCost>;
330331
fn cost_contract_hash(n: u64) -> InterpreterResult<ExecutionCost>;
332+
fn cost_to_ascii(n: u64) -> InterpreterResult<ExecutionCost>;
331333
}
332334

333335
impl ClarityCostFunction {
@@ -481,6 +483,7 @@ impl ClarityCostFunction {
481483
ClarityCostFunction::BitwiseLShift => C::cost_bitwise_left_shift(n),
482484
ClarityCostFunction::BitwiseRShift => C::cost_bitwise_right_shift(n),
483485
ClarityCostFunction::ContractHash => C::cost_contract_hash(n),
486+
ClarityCostFunction::ToAscii => C::cost_to_ascii(n),
484487
ClarityCostFunction::Unimplemented => Err(RuntimeErrorType::NotImplemented.into()),
485488
}
486489
}

clarity/src/vm/costs/costs_1.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,4 +749,8 @@ impl CostValues for Costs1 {
749749
fn cost_contract_hash(n: u64) -> InterpreterResult<ExecutionCost> {
750750
Err(RuntimeErrorType::NotImplemented.into())
751751
}
752+
753+
fn cost_to_ascii(n: u64) -> InterpreterResult<ExecutionCost> {
754+
Err(RuntimeErrorType::NotImplemented.into())
755+
}
752756
}

clarity/src/vm/costs/costs_2.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,4 +749,8 @@ impl CostValues for Costs2 {
749749
fn cost_contract_hash(n: u64) -> InterpreterResult<ExecutionCost> {
750750
Err(RuntimeErrorType::NotImplemented.into())
751751
}
752+
753+
fn cost_to_ascii(n: u64) -> InterpreterResult<ExecutionCost> {
754+
Err(RuntimeErrorType::NotImplemented.into())
755+
}
752756
}

0 commit comments

Comments
 (0)