diff --git a/clarity/src/vm/types/mod.rs b/clarity/src/vm/types/mod.rs index d0296a08ef..d4d347dfc4 100644 --- a/clarity/src/vm/types/mod.rs +++ b/clarity/src/vm/types/mod.rs @@ -1699,6 +1699,8 @@ pub fn byte_len_of_serialization(serialized: &str) -> u64 { #[cfg(test)] mod test { use super::*; + use crate::vm::errors::{Error, InterpreterError, RuntimeErrorType}; + #[test] fn test_constructors() { assert_eq!( @@ -1922,4 +1924,343 @@ mod test { .unwrap(); assert!(principal.is_multisig()); } + + #[test] + fn test_qualified_contract_identifier_local_returns_runtime_error() { + let err = QualifiedContractIdentifier::local("1nvalid-name") + .expect_err("Unexpected qualified contract identifier"); + assert_eq!( + Error::from(RuntimeErrorType::BadNameValue( + "ContractName", + "1nvalid-name".into() + )), + err, + ); + } + + #[rstest] + #[case::too_short("S162RK3CHJPCSSK6BM757FW", RuntimeErrorType::ParseError( + "Invalid principal literal: Expected 20 data bytes.".to_string(), + ))] + #[case::too_long("S1C5H66S35CSKK6CK1C9HP8SB6CWSK4RB2CDJK8HY4", RuntimeErrorType::ParseError( + "Invalid principal literal: Expected 20 data bytes.".to_string(), + ))] + #[case::invalid_c32("II2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G", RuntimeErrorType::ParseError( + "Invalid principal literal: base58ck checksum 0x1074d4f7 does not match expected 0xae29c6e0".to_string(), + ))] + fn test_principal_data_parse_standard_principal_returns_runtime_error( + #[case] input: &str, + #[case] expected_err: RuntimeErrorType, + ) { + let err = + PrincipalData::parse_standard_principal(input).expect_err("Unexpected principal data"); + assert_eq!(Error::from(expected_err), err); + } + + #[rstest] + #[case::no_dot("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0Gcontract-name", RuntimeErrorType::ParseError( + "Invalid principal literal: expected a `.` in a qualified contract name" + .to_string(), + ))] + #[case::invalid_contract_name("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G.1nvalid-name", RuntimeErrorType::BadNameValue("ContractName", "1nvalid-name".into()))] + + fn test_qualified_contract_identifier_parse_returns_interpreter_error( + #[case] input: &str, + #[case] expected_err: RuntimeErrorType, + ) { + let err = QualifiedContractIdentifier::parse(input) + .expect_err("Unexpected qualified contract identifier"); + assert_eq!(Error::from(expected_err), err); + } + + #[rstest] + #[case::no_dot("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-traitnft-trait", RuntimeErrorType::ParseError( + "Invalid principal literal: expected a `.` in a qualified contract name" + .to_string(), + ))] + #[case::invalid_contract_name("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.1nvalid-contract.valid-trait", RuntimeErrorType::BadNameValue("ContractName", "1nvalid-contract".into()))] + #[case::invalid_trait_name("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.valid-contract.1nvalid-trait", RuntimeErrorType::BadNameValue("ClarityName", "1nvalid-trait".into()))] + #[case::invalid_standard_principal("S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", RuntimeErrorType::ParseError( + "Invalid principal literal: Expected 20 data bytes.".to_string(), + ))] + fn test_trait_identifier_parse_returns_runtime_error( + #[case] input: &str, + #[case] expected_err: RuntimeErrorType, + ) { + let expected_err = Error::from(expected_err); + + let err = TraitIdentifier::parse(input).expect_err("Unexpected trait identifier"); + assert_eq!(expected_err, err); + + let err = + TraitIdentifier::parse_sugared_syntax(input).expect_err("Unexpected trait identifier"); + assert_eq!(expected_err, err); + } + + #[rstest] + #[case::bad_type_construction( + ".valid-contract.valid-trait", + RuntimeErrorType::BadTypeConstruction + )] + #[case::forwards_parse_errors("S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", RuntimeErrorType::ParseError( + "Invalid principal literal: Expected 20 data bytes.".to_string(), + ))] + fn test_trait_identifier_parse_fully_qualified_returns_runtime_error( + #[case] input: &str, + #[case] expected_err: RuntimeErrorType, + ) { + let err = + TraitIdentifier::parse_fully_qualified(input).expect_err("Unexpected trait identifier"); + assert_eq!(Error::from(expected_err), err); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_standard_principal_data_new_returns_interpreter_error_consensus_critical() { + let result = StandardPrincipalData::new(32, [0; 20]); + let err = result.expect_err("Unexpected principal data"); + + assert_eq!( + Error::from(InterpreterError::Expect("Unexpected principal data".into())), + err.into(), + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_sequence_data_element_at_returns_interpreter_error_consensus_critical() { + let buff = SequenceData::String(CharType::ASCII(ASCIIData { data: vec![1] })); + let err = buff.element_at(0).unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "BUG: failed to initialize single-byte ASCII buffer".into() + )), + err + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_ascii_data_to_value_returns_interpreter_error_consensus_critical() { + let err = ASCIIData::to_value(&1).unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "ERROR: Invalid ASCII string successfully constructed".into() + )), + err + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_utf8_data_to_value_returns_interpreter_error_consensus_critical() { + let err = UTF8Data::to_value(&vec![0xED, 0xA0, 0x80]).unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "ERROR: Invalid UTF8 string successfully constructed".into() + )), + err + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_tuple_data_from_data_typed_returns_interpreter_error_consensus_critical() { + let tuple_type = + TupleTypeSignature::try_from(vec![("a".into(), TypeSignature::IntType)]).unwrap(); + let err = TupleData::from_data_typed( + &StacksEpochId::Epoch32, + vec![("a".into(), Value::UInt(1))], + &tuple_type, + ) + .unwrap_err(); + assert_eq!( + Error::from(InterpreterError::FailureConstructingTupleWithType), + err + ); + } + + #[rstest] + #[case::not_a_string(Value::none(), InterpreterError::Expect("Expected ASCII string".to_string()))] + #[case::invalid_utf8(Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data: vec![0xED, 0xA0, 0x80] }))), InterpreterError::Expect("Non UTF-8 data in string".to_string()))] + fn test_value_expect_ascii_returns_interpreter_error( + #[case] value: Value, + #[case] expected_err: InterpreterError, + ) { + let err = value.expect_ascii().unwrap_err(); + assert_eq!(Error::from(expected_err), err); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_value_expect_u128_returns_interpreter_error_consensus_critical() { + let err = Value::none().expect_u128().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected u128".to_string())), + err + ); + } + + #[test] + fn test_value_expect_i128_returns_interpreter_error() { + let err = Value::none().expect_i128().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected i128".to_string())), + err + ); + } + + #[rstest] + #[case::not_a_buffer(Value::none(), InterpreterError::Expect("Expected buff".to_string()))] + #[case::too_small(Value::buff_from(vec![1, 2, 3, 4]).unwrap(), InterpreterError::Expect("Unexpected buff length".to_string()))] + fn test_value_expect_buff_returns_interpreter_error( + #[case] value: Value, + #[case] expected_err: InterpreterError, + ) { + let err = value.expect_buff(1).unwrap_err(); + assert_eq!(Error::from(expected_err), err); + } + + #[test] + fn test_value_expect_tuple_returns_interpreter_error() { + let err = Value::none().expect_tuple().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected tuple".to_string())), + err + ); + } + + #[test] + fn test_value_expect_list_returns_interpreter_error() { + let err = Value::none().expect_list().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected list".to_string())), + err + ); + } + + #[test] + fn test_value_expect_buff_padded_returns_interpreter_error() { + let err = Value::none().expect_buff_padded(10, 0).unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected buff".to_string())), + err + ); + } + + #[test] + fn test_value_expect_bool_returns_interpreter_error() { + let err = Value::none().expect_bool().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected bool".to_string())), + err + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_value_expect_optional_returns_interpreter_error_consensus_critical() { + let err = Value::okay_true().expect_optional().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected optional".to_string())), + err + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_value_expect_principal_returns_interpreter_error_consensus_critical() { + let err = Value::none().expect_principal().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected principal".to_string())), + err + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_value_expect_callable_returns_interpreter_error_consensus_critical() { + let err = Value::none().expect_callable().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected callable".to_string())), + err + ); + } + + #[test] + fn test_value_expect_result_returns_interpreter_error() { + let err = Value::none().expect_result().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect("Expected response".to_string())), + err + ); + } + + #[rstest] + #[case::not_a_response(Value::none(), InterpreterError::Expect("Expected response".to_string()))] + #[case::not_an_ok_response(Value::error(Value::Int(1)).unwrap(), InterpreterError::Expect("Expected ok response".to_string()))] + fn test_value_expect_result_ok_returns_interpreter_error( + #[case] value: Value, + #[case] expected_err: InterpreterError, + ) { + let err = value.expect_result_ok().unwrap_err(); + assert_eq!(Error::from(expected_err), err); + } + + #[rstest] + #[case::not_a_response(Value::none(), InterpreterError::Expect("Expected response".to_string()))] + #[case::not_an_err_response(Value::okay_true(), InterpreterError::Expect("Expected err response".to_string()))] + fn test_value_expect_result_err_returns_interpreter_error( + #[case] value: Value, + #[case] expected_err: InterpreterError, + ) { + let err = value.expect_result_err().unwrap_err(); + assert_eq!(Error::from(expected_err), err); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_buff_data_len_returns_interpreter_error_consensus_critical() { + let err = BuffData { + data: vec![1; MAX_VALUE_SIZE as usize + 1], + } + .len() + .unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "Data length should be valid".into() + )), + err + ); + } + + #[test] + fn test_ascii_data_len_returns_interpreter_error() { + let err = ASCIIData { + data: vec![1; MAX_VALUE_SIZE as usize + 1], + } + .len() + .unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "Data length should be valid".into() + )), + err + ); + } + + #[test] + fn test_utf8_data_len_returns_interpreter_error() { + let err = UTF8Data { + data: vec![vec![]; MAX_VALUE_SIZE as usize + 1], + } + .len() + .unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "Data length should be valid".into() + )), + err + ); + } } diff --git a/clarity/src/vm/types/serialization.rs b/clarity/src/vm/types/serialization.rs index 95d3208065..dcef98cf7a 100644 --- a/clarity/src/vm/types/serialization.rs +++ b/clarity/src/vm/types/serialization.rs @@ -1370,6 +1370,7 @@ pub mod tests { use super::super::*; use super::SerializationError; use crate::vm::database::{ClarityDeserializable, ClaritySerializable, RollbackWrapper}; + use crate::vm::errors::{Error, InterpreterError}; use crate::vm::tests::test_clarity_versions; use crate::vm::ClarityVersion; @@ -2199,4 +2200,34 @@ pub mod tests { test_bad_expectation(contract_p2, TypeSignature::BoolType); test_bad_expectation(standard_p, TypeSignature::BoolType); } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_serialize_to_vec_returns_interpreter_error_consensus_critical() { + let value = Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { + data: vec![0; MAX_VALUE_SIZE as usize + 1], + }))); + let err = value.serialize_to_vec().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "IOError filling byte buffer.".into() + )), + err.into() + ); + } + + /// The returned InterpreterError is consensus-critical. + #[test] + fn test_serialize_to_hex_returns_interpreter_error_consensus_critical() { + let value = Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { + data: vec![0; MAX_VALUE_SIZE as usize + 1], + }))); + let err = value.serialize_to_hex().unwrap_err(); + assert_eq!( + Error::from(InterpreterError::Expect( + "IOError filling byte buffer.".into() + )), + err.into() + ); + } } diff --git a/clarity/src/vm/types/signatures.rs b/clarity/src/vm/types/signatures.rs index effe950a8b..01a5531840 100644 --- a/clarity/src/vm/types/signatures.rs +++ b/clarity/src/vm/types/signatures.rs @@ -2683,4 +2683,15 @@ mod test { ); } } + + #[test] + fn test_type_signature_bound_string_ascii_type_returns_check_errors() { + let err = TypeSignature::bound_string_ascii_type(MAX_VALUE_SIZE + 1).unwrap_err(); + assert_eq!( + CheckErrors::Expects( + "FAIL: Max Clarity Value Size is no longer realizable in ASCII Type".to_string() + ), + err + ); + } }