diff --git a/Cargo.lock b/Cargo.lock index 482b228034..67a0ce3bf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3326,6 +3326,7 @@ dependencies = [ "stacks-common 0.0.1", "stackslib 0.0.1", "tempfile", + "thiserror", ] [[package]] diff --git a/clarity-types/src/errors/analysis.rs b/clarity-types/src/errors/analysis.rs index 29bdb3bcb2..8ae5026751 100644 --- a/clarity-types/src/errors/analysis.rs +++ b/clarity-types/src/errors/analysis.rs @@ -19,7 +19,7 @@ use crate::diagnostic::{DiagnosableError, Diagnostic}; use crate::errors::CostErrors; use crate::execution_cost::ExecutionCost; use crate::representations::SymbolicExpression; -use crate::types::{TraitIdentifier, TupleTypeSignature, TypeSignature, Value}; +use crate::types::{ClarityTypeError, TraitIdentifier, TupleTypeSignature, TypeSignature, Value}; /// What kind of syntax binding was found to be in error? #[derive(Debug, PartialEq, Clone, Copy)] @@ -200,7 +200,11 @@ pub enum CommonCheckErrorKind { // Unexpected interpreter behavior /// Unexpected condition or failure in the type-checker, indicating a bug or invalid state. /// This error indicates a transaction would invalidate a block if included. - Expects(String), + ExpectsRejectable(String), + // Unexpected interpreter behavior + /// Unexpected condition or failure in the type-checker, indicating a bug or invalid state. + /// This error does NOT indicate a transaction would invalidate a block if included. + ExpectsAcceptable(String), // Type mismatch errors /// Expected type does not match the actual type during analysis. @@ -293,7 +297,11 @@ pub enum StaticCheckErrorKind { // Unexpected interpreter behavior /// Unexpected condition or failure in the type-checker, indicating a bug or invalid state. /// This error indicates a transaction would invalidate a block if included. - Expects(String), + ExpectsRejectable(String), + // Unexpected interpreter behavior + /// Unexpected condition or failure in the type-checker, indicating a bug or invalid state. + /// This error does NOT indicate a transaction would invalidate a block if included. + ExpectsAcceptable(String), // Match expression errors /// Invalid syntax in an `option` match expression. @@ -606,7 +614,11 @@ pub enum CheckErrorKind { // Unexpected interpreter behavior /// Unexpected condition or failure in the type-checker, indicating a bug or invalid state. /// This error indicates a transaction would invalidate a block if included. - Expects(String), + ExpectsRejectable(String), + // Unexpected interpreter behavior + /// Unexpected condition or failure in the type-checker, indicating a bug or invalid state. + /// This error does NOT indicate a transaction would invalidate a block if included. + ExpectsAcceptable(String), // Match expression errors /// Invalid syntax in an `option` match expression. @@ -760,7 +772,7 @@ pub enum CheckErrorKind { /// Expected a list application but found a different expression. ExpectedListApplication, /// Expected a sequence type (e.g., list, buffer) but found a different type. - /// The `Box` wraps the actual type provided. + /// The `Box` wraps the actual type provided if we know it. ExpectedSequence(Box), // Let syntax @@ -863,7 +875,7 @@ impl CheckErrorKind { pub fn rejectable(&self) -> bool { matches!( self, - CheckErrorKind::SupertypeTooLarge | CheckErrorKind::Expects(_) + CheckErrorKind::SupertypeTooLarge | CheckErrorKind::ExpectsRejectable(_) ) } } @@ -873,7 +885,7 @@ impl StaticCheckErrorKind { pub fn rejectable(&self) -> bool { matches!( self, - StaticCheckErrorKind::SupertypeTooLarge | StaticCheckErrorKind::Expects(_) + StaticCheckErrorKind::SupertypeTooLarge | StaticCheckErrorKind::ExpectsRejectable(_) ) } } @@ -909,6 +921,60 @@ impl StaticCheckError { } } +impl From for StaticCheckErrorKind { + fn from(err: ClarityTypeError) -> Self { + match err { + ClarityTypeError::ValueTooLarge => Self::ValueTooLarge, + ClarityTypeError::TypeSignatureTooDeep => Self::TypeSignatureTooDeep, + ClarityTypeError::ValueOutOfBounds => Self::ValueOutOfBounds, + ClarityTypeError::DuplicateTupleField(name) => Self::NameAlreadyUsed(name), + ClarityTypeError::NoSuchTupleField(field, tuple_sig) => { + Self::NoSuchTupleField(field, tuple_sig) + } + ClarityTypeError::TypeMismatch(expected, found) => Self::TypeError(expected, found), + ClarityTypeError::EmptyTuplesNotAllowed => Self::EmptyTuplesNotAllowed, + ClarityTypeError::SupertypeTooLarge => Self::SupertypeTooLarge, + ClarityTypeError::InvalidTypeDescription => Self::InvalidTypeDescription, + ClarityTypeError::InvalidUrlString(_) + | ClarityTypeError::InvalidClarityName(_) + | ClarityTypeError::InvalidContractName(_) + | ClarityTypeError::QualifiedContractEmptyIssuer + | ClarityTypeError::QualifiedContractMissingDot + | ClarityTypeError::InvalidPrincipalEncoding(_) + | ClarityTypeError::InvalidPrincipalLength(_) + | ClarityTypeError::ListTypeMismatch + | ClarityTypeError::SequenceElementArityMismatch { .. } + | ClarityTypeError::ExpectedSequenceValue + | ClarityTypeError::TypeMismatchValue(_, _) + | ClarityTypeError::ResponseTypeMismatch { .. } + | ClarityTypeError::InvalidAsciiCharacter(_) + | ClarityTypeError::InvalidUtf8Encoding => Self::ExpectsAcceptable(format!( + "Unexpected error type during static analysis: {err}" + )), + ClarityTypeError::InvariantViolation(_) + | ClarityTypeError::InvalidPrincipalVersion(_) => Self::ExpectsRejectable(format!( + "Unexpected error type during static analysis: {err}" + )), + ClarityTypeError::CouldNotDetermineSerializationType => { + Self::CouldNotDetermineSerializationType + } + ClarityTypeError::CouldNotDetermineType => Self::CouldNotDetermineType, + ClarityTypeError::UnsupportedTypeInEpoch(ty, epoch) => { + Self::ExpectsRejectable(format!("{ty} should not be used in {epoch}")) + } + ClarityTypeError::UnsupportedEpoch(epoch) => { + Self::ExpectsRejectable(format!("{epoch} is not supported")) + } + } + } +} + +impl From for StaticCheckError { + fn from(err: ClarityTypeError) -> Self { + StaticCheckErrorKind::from(err).into() + } +} + impl From<(CommonCheckErrorKind, &SymbolicExpression)> for StaticCheckError { fn from(e: (CommonCheckErrorKind, &SymbolicExpression)) -> Self { Self::with_expression(e.0.into(), e.1) @@ -933,6 +999,96 @@ impl From<(CommonCheckErrorKind, &SymbolicExpression)> for CheckErrorKind { } } +impl From for CheckErrorKind { + fn from(err: ClarityTypeError) -> Self { + match err { + ClarityTypeError::ValueTooLarge => Self::ValueTooLarge, + ClarityTypeError::TypeSignatureTooDeep => Self::TypeSignatureTooDeep, + ClarityTypeError::ValueOutOfBounds => Self::ValueOutOfBounds, + ClarityTypeError::DuplicateTupleField(name) => Self::NameAlreadyUsed(name), + ClarityTypeError::NoSuchTupleField(field, tuple_sig) => { + Self::NoSuchTupleField(field, tuple_sig) + } + ClarityTypeError::TypeMismatchValue(ty, value) => Self::TypeValueError(ty, value), + ClarityTypeError::TypeMismatch(expected, found) => Self::TypeError(expected, found), + ClarityTypeError::EmptyTuplesNotAllowed => Self::EmptyTuplesNotAllowed, + ClarityTypeError::SupertypeTooLarge => Self::SupertypeTooLarge, + ClarityTypeError::InvalidTypeDescription => Self::InvalidTypeDescription, + ClarityTypeError::ListTypeMismatch => Self::ListTypesMustMatch, + ClarityTypeError::InvalidAsciiCharacter(_) => Self::InvalidCharactersDetected, + ClarityTypeError::InvalidUtf8Encoding => Self::InvalidUTF8Encoding, + ClarityTypeError::ExpectedSequenceValue + | ClarityTypeError::SequenceElementArityMismatch { .. } + | ClarityTypeError::CouldNotDetermineSerializationType + | ClarityTypeError::InvalidUrlString(_) + | ClarityTypeError::InvalidClarityName(_) + | ClarityTypeError::InvalidContractName(_) + | ClarityTypeError::QualifiedContractEmptyIssuer + | ClarityTypeError::QualifiedContractMissingDot + | ClarityTypeError::InvalidPrincipalEncoding(_) + | ClarityTypeError::InvalidPrincipalLength(_) + | ClarityTypeError::ResponseTypeMismatch { .. } => Self::ExpectsAcceptable(format!( + "Unexpected error type during runtime analysis: {err}" + )), + ClarityTypeError::InvariantViolation(_) + | ClarityTypeError::InvalidPrincipalVersion(_) => Self::ExpectsRejectable(format!( + "Unexpected error type during runtime analysis: {err}" + )), + ClarityTypeError::CouldNotDetermineType => Self::CouldNotDetermineType, + ClarityTypeError::UnsupportedTypeInEpoch(ty, epoch) => { + Self::ExpectsRejectable(format!("{ty} should not be used in {epoch}")) + } + ClarityTypeError::UnsupportedEpoch(epoch) => { + Self::ExpectsRejectable(format!("{epoch} is not supported")) + } + } + } +} + +impl From for CommonCheckErrorKind { + fn from(err: ClarityTypeError) -> Self { + match err { + ClarityTypeError::ValueTooLarge => Self::ValueTooLarge, + ClarityTypeError::TypeSignatureTooDeep => Self::TypeSignatureTooDeep, + ClarityTypeError::ValueOutOfBounds => Self::ValueOutOfBounds, + ClarityTypeError::DuplicateTupleField(name) => Self::NameAlreadyUsed(name), + ClarityTypeError::TypeMismatch(expected, found) => Self::TypeError(expected, found), + ClarityTypeError::EmptyTuplesNotAllowed => Self::EmptyTuplesNotAllowed, + ClarityTypeError::SupertypeTooLarge => Self::SupertypeTooLarge, + ClarityTypeError::InvalidTypeDescription => Self::InvalidTypeDescription, + ClarityTypeError::CouldNotDetermineType => Self::CouldNotDetermineType, + ClarityTypeError::ListTypeMismatch + | ClarityTypeError::SequenceElementArityMismatch { .. } + | ClarityTypeError::ExpectedSequenceValue + | ClarityTypeError::InvalidAsciiCharacter(_) + | ClarityTypeError::InvalidUtf8Encoding + | ClarityTypeError::NoSuchTupleField(_, _) + | ClarityTypeError::TypeMismatchValue(_, _) + | ClarityTypeError::CouldNotDetermineSerializationType + | ClarityTypeError::InvalidUrlString(_) + | ClarityTypeError::InvalidClarityName(_) + | ClarityTypeError::InvalidContractName(_) + | ClarityTypeError::QualifiedContractEmptyIssuer + | ClarityTypeError::QualifiedContractMissingDot + | ClarityTypeError::InvalidPrincipalEncoding(_) + | ClarityTypeError::InvalidPrincipalLength(_) + | ClarityTypeError::ResponseTypeMismatch { .. } => { + Self::ExpectsAcceptable(format!("Unexpected error type during analysis: {err}")) + } + ClarityTypeError::InvariantViolation(_) + | ClarityTypeError::InvalidPrincipalVersion(_) => { + Self::ExpectsRejectable(format!("Unexpected error type during analysis: {err}")) + } + ClarityTypeError::UnsupportedTypeInEpoch(ty, epoch) => { + Self::ExpectsRejectable(format!("{ty} should not be used in {epoch}")) + } + ClarityTypeError::UnsupportedEpoch(epoch) => { + Self::ExpectsRejectable(format!("{epoch} is not supported")) + } + } + } +} + impl fmt::Display for CommonCheckErrorKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{self:?}") @@ -983,10 +1139,10 @@ impl From for StaticCheckErrorKind { CostErrors::CostContractLoadFailure => { StaticCheckErrorKind::CostComputationFailed("Failed to load cost contract".into()) } - CostErrors::InterpreterFailure => StaticCheckErrorKind::Expects( + CostErrors::InterpreterFailure => StaticCheckErrorKind::ExpectsRejectable( "Unexpected interpreter failure in cost computation".into(), ), - CostErrors::Expect(s) => StaticCheckErrorKind::Expects(s), + CostErrors::Expect(s) => StaticCheckErrorKind::ExpectsRejectable(s), CostErrors::ExecutionTimeExpired => StaticCheckErrorKind::ExecutionTimeExpired, } } @@ -1002,10 +1158,10 @@ impl From for CheckErrorKind { CostErrors::CostContractLoadFailure => { CheckErrorKind::CostComputationFailed("Failed to load cost contract".into()) } - CostErrors::InterpreterFailure => { - CheckErrorKind::Expects("Unexpected interpreter failure in cost computation".into()) - } - CostErrors::Expect(s) => CheckErrorKind::Expects(s), + CostErrors::InterpreterFailure => CheckErrorKind::ExpectsRejectable( + "Unexpected interpreter failure in cost computation".into(), + ), + CostErrors::Expect(s) => CheckErrorKind::ExpectsRejectable(s), CostErrors::ExecutionTimeExpired => CheckErrorKind::ExecutionTimeExpired, } } @@ -1025,10 +1181,10 @@ impl From for CommonCheckErrorKind { CostErrors::CostContractLoadFailure => { CommonCheckErrorKind::CostComputationFailed("Failed to load cost contract".into()) } - CostErrors::InterpreterFailure => CommonCheckErrorKind::Expects( + CostErrors::InterpreterFailure => CommonCheckErrorKind::ExpectsRejectable( "Unexpected interpreter failure in cost computation".into(), ), - CostErrors::Expect(s) => CommonCheckErrorKind::Expects(s), + CostErrors::Expect(s) => CommonCheckErrorKind::ExpectsRejectable(s), CostErrors::ExecutionTimeExpired => CommonCheckErrorKind::ExecutionTimeExpired, } } @@ -1097,7 +1253,8 @@ impl From for CheckErrorKind { CommonCheckErrorKind::ExpectedTraitIdentifier => { CheckErrorKind::ExpectedTraitIdentifier } - CommonCheckErrorKind::Expects(s) => CheckErrorKind::Expects(s), + CommonCheckErrorKind::ExpectsRejectable(s) => CheckErrorKind::ExpectsRejectable(s), + CommonCheckErrorKind::ExpectsAcceptable(s) => CheckErrorKind::ExpectsAcceptable(s), CommonCheckErrorKind::CouldNotDetermineType => CheckErrorKind::CouldNotDetermineType, CommonCheckErrorKind::ValueTooLarge => CheckErrorKind::ValueTooLarge, CommonCheckErrorKind::TypeSignatureTooDeep => CheckErrorKind::TypeSignatureTooDeep, @@ -1157,7 +1314,12 @@ impl From for StaticCheckErrorKind { CommonCheckErrorKind::ExpectedTraitIdentifier => { StaticCheckErrorKind::ExpectedTraitIdentifier } - CommonCheckErrorKind::Expects(s) => StaticCheckErrorKind::Expects(s), + CommonCheckErrorKind::ExpectsRejectable(s) => { + StaticCheckErrorKind::ExpectsRejectable(s) + } + CommonCheckErrorKind::ExpectsAcceptable(s) => { + StaticCheckErrorKind::ExpectsAcceptable(s) + } CommonCheckErrorKind::CouldNotDetermineType => { StaticCheckErrorKind::CouldNotDetermineType } @@ -1258,7 +1420,8 @@ impl DiagnosableError for StaticCheckErrorKind { fn message(&self) -> String { match &self { StaticCheckErrorKind::SupertypeTooLarge => "supertype of two types is too large".into(), - StaticCheckErrorKind::Expects(s) => format!("unexpected interpreter behavior: {s}"), + StaticCheckErrorKind::ExpectsRejectable(s) => format!("unexpected interpreter behavior: {s}"), + StaticCheckErrorKind::ExpectsAcceptable(s) => s.clone(), StaticCheckErrorKind::BadMatchOptionSyntax(source) => format!("match on a optional type uses the following syntax: (match input some-name if-some-expression if-none-expression). Caused by: {}", source.message()), diff --git a/clarity-types/src/errors/mod.rs b/clarity-types/src/errors/mod.rs index 4db200c312..f38195eef5 100644 --- a/clarity-types/src/errors/mod.rs +++ b/clarity-types/src/errors/mod.rs @@ -28,6 +28,7 @@ pub use lexer::LexerError; use rusqlite::Error as SqliteError; use stacks_common::types::chainstate::BlockHeaderHash; +use crate::ClarityTypeError; use crate::representations::SymbolicExpression; use crate::types::{FunctionIdentifier, Value}; @@ -160,8 +161,6 @@ pub enum RuntimeError { MaxStackDepthReached, /// The execution context depth exceeded the virtual machine's limit. MaxContextDepthReached, - /// Attempt to construct an invalid or unsupported type at runtime (e.g., malformed data structure). - BadTypeConstruction, /// Reference to an invalid or out-of-bounds block height. /// The `String` represents the string representation of the queried block height that was invalid. BadBlockHeight(String), @@ -173,9 +172,6 @@ pub enum RuntimeError { NoCallerInContext, /// No sender principal available in the current execution context. NoSenderInContext, - /// Invalid name-value pair in contract data (e.g., map keys). - /// The `&'static str` represents the name of the invalid pair, and the `String` represents the offending value. - BadNameValue(&'static str, String), /// Reference to a non-existent block header hash. /// The `BlockHeaderHash` represents the unknown block header hash. UnknownBlockHeaderHash(BlockHeaderHash), @@ -261,6 +257,12 @@ impl error::Error for RuntimeError { } } +impl From for VmExecutionError { + fn from(err: ClarityTypeError) -> Self { + Self::from(CheckErrorKind::from(err)) + } +} + impl From for VmExecutionError { fn from(err: ParseError) -> Self { match *err.err { diff --git a/clarity-types/src/lib.rs b/clarity-types/src/lib.rs index 68cc269b47..88618db39c 100644 --- a/clarity-types/src/lib.rs +++ b/clarity-types/src/lib.rs @@ -33,7 +33,7 @@ pub mod types; pub use errors::VmExecutionError; pub use representations::{ClarityName, ContractName}; -pub use types::Value; +pub use types::{ClarityTypeError, Value}; pub const MAX_CALL_STACK_DEPTH: usize = 64; diff --git a/clarity-types/src/representations.rs b/clarity-types/src/representations.rs index 86275e25a4..dfda3225fb 100644 --- a/clarity-types/src/representations.rs +++ b/clarity-types/src/representations.rs @@ -23,8 +23,7 @@ use regex::Regex; use stacks_common::codec::{Error as codec_error, StacksMessageCodec, read_next, write_next}; use crate::Value; -use crate::errors::RuntimeError; -use crate::types::TraitIdentifier; +use crate::types::{ClarityTypeError, TraitIdentifier}; pub const CONTRACT_MIN_NAME_LENGTH: usize = 1; pub const CONTRACT_MAX_NAME_LENGTH: usize = 40; @@ -63,20 +62,18 @@ lazy_static! { guarded_string!( ClarityName, - "ClarityName", CLARITY_NAME_REGEX, MAX_STRING_LEN, - RuntimeError, - RuntimeError::BadNameValue + ClarityTypeError, + ClarityTypeError::InvalidClarityName ); guarded_string!( ContractName, - "ContractName", CONTRACT_NAME_REGEX, MAX_STRING_LEN, - RuntimeError, - RuntimeError::BadNameValue + ClarityTypeError, + ClarityTypeError::InvalidContractName ); impl StacksMessageCodec for ClarityName { diff --git a/clarity-types/src/tests/representations.rs b/clarity-types/src/tests/representations.rs index 0d00f7bc87..166dc8e6ab 100644 --- a/clarity-types/src/tests/representations.rs +++ b/clarity-types/src/tests/representations.rs @@ -15,11 +15,11 @@ use rstest::rstest; -use crate::errors::RuntimeError; use crate::representations::{ CONTRACT_MAX_NAME_LENGTH, CONTRACT_MIN_NAME_LENGTH, ClarityName, ContractName, MAX_STRING_LEN, }; use crate::stacks_common::codec::StacksMessageCodec; +use crate::types::ClarityTypeError; #[rstest] #[case::valid_name("hello")] @@ -73,7 +73,7 @@ fn test_clarity_name_invalid(#[case] name: &str) { assert!(result.is_err()); assert!(matches!( result.unwrap_err(), - RuntimeError::BadNameValue(_, _) + ClarityTypeError::InvalidClarityName(_) )); } @@ -99,7 +99,7 @@ fn test_clarity_name_serialization(#[case] name: &str) { // the first byte is the length of the buffer. #[rstest] #[case::invalid_utf8(vec![4, 0xFF, 0xFE, 0xFD, 0xFC], "Failed to parse Clarity name: could not contruct from utf8")] -#[case::invalid_name(vec![2, b'2', b'i'], "Failed to parse Clarity name: BadNameValue(\"ClarityName\", \"2i\")")] // starts with number +#[case::invalid_name(vec![2, b'2', b'i'], "Failed to parse Clarity name: InvalidClarityName(\"2i\")")] // starts with number #[case::too_long(vec![MAX_STRING_LEN + 1], "Failed to deserialize clarity name: too long")] #[case::wrong_length(vec![3, b'a'], "failed to fill whole buffer")] fn test_clarity_name_deserialization_errors(#[case] buffer: Vec, #[case] error_message: &str) { @@ -157,7 +157,7 @@ fn test_contract_name_invalid(#[case] name: &str) { assert!(result.is_err()); assert!(matches!( result.unwrap_err(), - RuntimeError::BadNameValue(_, _) + ClarityTypeError::InvalidContractName(_) )); } @@ -201,7 +201,7 @@ fn test_contract_name_serialization_too_long() { // the first byte is the length of the buffer. #[rstest] #[case::invalid_utf8(vec![4, 0xFF, 0xFE, 0xFD, 0xFC], "Failed to parse Contract name: could not construct from utf8")] -#[case::invalid_name(vec![2, b'2', b'i'], "Failed to parse Contract name: BadNameValue(\"ContractName\", \"2i\")")] // starts with number +#[case::invalid_name(vec![2, b'2', b'i'], "Failed to parse Contract name: InvalidContractName(\"2i\")")] // starts with number #[case::too_long(vec![MAX_STRING_LEN + 1], &format!("Failed to deserialize contract name: too short or too long: {}", MAX_STRING_LEN + 1))] #[case::wrong_length(vec![3, b'a'], "failed to fill whole buffer")] fn test_contract_name_deserialization_errors(#[case] buffer: Vec, #[case] error_message: &str) { diff --git a/clarity-types/src/tests/types/mod.rs b/clarity-types/src/tests/types/mod.rs index 1028d443a2..d977afa684 100644 --- a/clarity-types/src/tests/types/mod.rs +++ b/clarity-types/src/tests/types/mod.rs @@ -18,13 +18,11 @@ mod signatures; use rstest::rstest; use stacks_common::types::StacksEpochId; -use crate::VmExecutionError; -use crate::errors::analysis::CommonCheckErrorKind; -use crate::errors::{CheckErrorKind, RuntimeError, VmInternalError}; use crate::types::{ - ASCIIData, BuffData, CharType, ListTypeData, MAX_VALUE_SIZE, PrincipalData, - QualifiedContractIdentifier, SequenceData, SequencedValue as _, StandardPrincipalData, - TraitIdentifier, TupleData, TupleTypeSignature, TypeSignature, UTF8Data, Value, + ASCIIData, BuffData, CharType, ClarityTypeError, ListTypeData, MAX_VALUE_SIZE, PrincipalData, + QualifiedContractIdentifier, SequenceData, SequenceSubtype, SequencedValue as _, + StandardPrincipalData, TraitIdentifier, TupleData, TupleTypeSignature, TypeSignature, UTF8Data, + Value, }; #[test] @@ -35,16 +33,16 @@ fn test_constructors() { vec![Value::Int(5), Value::Int(2)], ListTypeData::new_list(TypeSignature::BoolType, 3).unwrap() ), - Err(VmInternalError::FailureConstructingListWithType.into()) + Err(ClarityTypeError::ListTypeMismatch) ); assert_eq!( ListTypeData::new_list(TypeSignature::IntType, MAX_VALUE_SIZE), - Err(CommonCheckErrorKind::ValueTooLarge) + Err(ClarityTypeError::ValueTooLarge) ); assert_eq!( Value::buff_from(vec![0; (MAX_VALUE_SIZE + 1) as usize]), - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) ); // Test that wrappers (okay, error, some) @@ -53,17 +51,17 @@ fn test_constructors() { // isn't causing the error). assert_eq!( Value::okay(Value::buff_from(vec![0; (MAX_VALUE_SIZE) as usize]).unwrap()), - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) ); assert_eq!( Value::error(Value::buff_from(vec![0; (MAX_VALUE_SIZE) as usize]).unwrap()), - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) ); assert_eq!( Value::some(Value::buff_from(vec![0; (MAX_VALUE_SIZE) as usize]).unwrap()), - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) ); // Test that the depth limit is correctly enforced: @@ -87,24 +85,24 @@ fn test_constructors() { let inner_value = cons().unwrap(); assert_eq!( TupleData::from_data(vec![("a".into(), inner_value.clone())]), - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) ); assert_eq!( Value::list_from(vec![inner_value.clone()]), - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) ); assert_eq!( Value::okay(inner_value.clone()), - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) ); assert_eq!( Value::error(inner_value.clone()), - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) ); assert_eq!( Value::some(inner_value), - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) ); if std::env::var("CIRCLE_TESTING") == Ok("1".to_string()) { @@ -116,7 +114,7 @@ fn test_constructors() { if (u32::MAX as usize) < usize::MAX { assert_eq!( Value::buff_from(vec![0; (u32::MAX as usize) + 10]), - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) ); } } @@ -256,65 +254,60 @@ fn test_qualified_contract_identifier_local_returns_runtime_error() { let err = QualifiedContractIdentifier::local("1nvalid-name") .expect_err("Unexpected qualified contract identifier"); assert_eq!( - VmExecutionError::from(RuntimeError::BadNameValue( - "ContractName", - "1nvalid-name".into() - )), + ClarityTypeError::InvalidContractName("1nvalid-name".into()), err, ); } #[rstest] -#[case::too_short("S162RK3CHJPCSSK6BM757FW", RuntimeError::TypeParseFailure( - "Invalid principal literal: Expected 20 data bytes.".to_string(), +#[case::too_short("S162RK3CHJPCSSK6BM757FW", ClarityTypeError::InvalidPrincipalLength(9))] +#[case::too_long( + "S1C5H66S35CSKK6CK1C9HP8SB6CWSK4RB2CDJK8HY4", + ClarityTypeError::InvalidPrincipalLength(21) +)] +#[case::invalid_c32("II2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G", ClarityTypeError::InvalidPrincipalEncoding( + "base58ck checksum 0x1074d4f7 does not match expected 0xae29c6e0".into(), ))] -#[case::too_long("S1C5H66S35CSKK6CK1C9HP8SB6CWSK4RB2CDJK8HY4", RuntimeError::TypeParseFailure( - "Invalid principal literal: Expected 20 data bytes.".to_string(), -))] -#[case::invalid_c32("II2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G", RuntimeError::TypeParseFailure( - "Invalid principal literal: base58ck checksum 0x1074d4f7 does not match expected 0xae29c6e0".to_string(), -))] -fn test_principal_data_parse_standard_principal_returns_runtime_error( +fn test_principal_data_parse_standard_principal_returns_clarity_type_error( #[case] input: &str, - #[case] expected_err: RuntimeError, + #[case] expected_err: ClarityTypeError, ) { - let err = - PrincipalData::parse_standard_principal(input).expect_err("Unexpected principal data"); - assert_eq!(VmExecutionError::from(expected_err), err); + let err = PrincipalData::parse_standard_principal(input) + .expect_err("Unexpected valid principal data"); + assert_eq!(expected_err, err); } #[rstest] -#[case::no_dot("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0Gcontract-name", RuntimeError::TypeParseFailure( - "Invalid principal literal: expected a `.` in a qualified contract name" - .to_string(), -))] -#[case::invalid_contract_name("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G.1nvalid-name", RuntimeError::BadNameValue("ContractName", "1nvalid-name".into()))] +#[case::no_dot( + "SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0Gcontract-name", + ClarityTypeError::QualifiedContractMissingDot +)] +#[case::invalid_contract_name("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G.1nvalid-name", ClarityTypeError::InvalidContractName("1nvalid-name".into()))] -fn test_qualified_contract_identifier_parse_returns_vm_internal_error( +fn test_qualified_contract_identifier_parse_returns_clarity_type_error( #[case] input: &str, - #[case] expected_err: RuntimeError, + #[case] expected_err: ClarityTypeError, ) { let err = QualifiedContractIdentifier::parse(input) .expect_err("Unexpected qualified contract identifier"); - assert_eq!(VmExecutionError::from(expected_err), err); + assert_eq!(expected_err, err); } #[rstest] -#[case::no_dot("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-traitnft-trait", RuntimeError::TypeParseFailure( - "Invalid principal literal: expected a `.` in a qualified contract name" - .to_string(), -))] -#[case::invalid_contract_name("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.1nvalid-contract.valid-trait", RuntimeError::BadNameValue("ContractName", "1nvalid-contract".into()))] -#[case::invalid_trait_name("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.valid-contract.1nvalid-trait", RuntimeError::BadNameValue("ClarityName", "1nvalid-trait".into()))] -#[case::invalid_standard_principal("S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", RuntimeError::TypeParseFailure( - "Invalid principal literal: Expected 20 data bytes.".to_string(), -))] -fn test_trait_identifier_parse_returns_runtime_error( +#[case::no_dot( + "SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-traitnft-trait", + ClarityTypeError::QualifiedContractMissingDot +)] +#[case::invalid_contract_name("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.1nvalid-contract.valid-trait", ClarityTypeError::InvalidContractName("1nvalid-contract".into()))] +#[case::invalid_trait_name("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.valid-contract.1nvalid-trait", ClarityTypeError::InvalidClarityName("1nvalid-trait".into()))] +#[case::invalid_standard_principal( + "S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", + ClarityTypeError::InvalidPrincipalLength(9) +)] +fn test_trait_identifier_parse_returns_clarity_type_error( #[case] input: &str, - #[case] expected_err: RuntimeError, + #[case] expected_err: ClarityTypeError, ) { - let expected_err = VmExecutionError::from(expected_err); - let err = TraitIdentifier::parse(input).expect_err("Unexpected trait identifier"); assert_eq!(expected_err, err); @@ -324,71 +317,65 @@ fn test_trait_identifier_parse_returns_runtime_error( } #[rstest] -#[case::bad_type_construction(".valid-contract.valid-trait", RuntimeError::BadTypeConstruction)] -#[case::forwards_parse_errors("S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", RuntimeError::TypeParseFailure( - "Invalid principal literal: Expected 20 data bytes.".to_string(), -))] -fn test_trait_identifier_parse_fully_qualified_returns_runtime_error( +#[case::bad_type_construction( + ".valid-contract.valid-trait", + ClarityTypeError::QualifiedContractEmptyIssuer +)] +#[case::forwards_parse_errors( + "S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", + ClarityTypeError::InvalidPrincipalLength(9) +)] +fn test_trait_identifier_parse_fully_qualified_returns_clarity_type_error( #[case] input: &str, - #[case] expected_err: RuntimeError, + #[case] expected_err: ClarityTypeError, ) { let err = TraitIdentifier::parse_fully_qualified(input).expect_err("Unexpected trait identifier"); - assert_eq!(VmExecutionError::from(expected_err), err); + assert_eq!(expected_err, err); } -/// The returned VMInternalError is consensus-critical. +// TODO: remove this comment. Is this truly consensus critical? i.e. if it just returns an error, does it matter if it +// maintains that its a rejectable block +/// The returned ClarityTypeError::InvalidPrincipalVersion is consensus-critical. #[test] -fn test_standard_principal_data_new_returns_vm_internal_error_consensus_critical() { +fn test_standard_principal_data_new_returns_clarity_type_error_invalid_principal_version_error_consensus_critical() + { let result = StandardPrincipalData::new(32, [0; 20]); - let err = result.expect_err("Unexpected principal data"); + let err = result.expect_err("Unexpected valid principal data"); - assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Unexpected principal data".into())), - err.into(), - ); + assert_eq!(ClarityTypeError::InvalidPrincipalVersion(32), err,); } -/// The returned VMInternalError is consensus-critical. +// TODO: remove this comment. Is this truly consensus critical? i.e. if it just returns an error, does it matter if it +// maintains that its a rejectable block? Currently all calls to elemant_at are converted to an VmInternal::Expects #[test] -fn test_sequence_data_element_at_returns_vm_internal_error_consensus_critical() { +fn test_sequence_data_element_at_returns_clarity_type_error_consensus_critical() { let buff = SequenceData::String(CharType::ASCII(ASCIIData { data: vec![1] })); let err = buff.element_at(0).unwrap_err(); - assert_eq!( - VmExecutionError::from(VmInternalError::Expect( - "BUG: failed to initialize single-byte ASCII buffer".into() - )), - err - ); + assert_eq!(ClarityTypeError::InvalidAsciiCharacter(1), err); } -/// The returned VMInternalError is consensus-critical. +// TODO: remove this comment. Is this truly consensus critical? i.e. if it just returns an error, does it matter if it +// maintains that its a rejectable block #[test] -fn test_ascii_data_to_value_returns_vm_internal_error_consensus_critical() { +fn test_ascii_data_to_value_returns_clarity_type_error() { let err = ASCIIData::to_value(&1).unwrap_err(); - assert_eq!( - VmExecutionError::from(VmInternalError::Expect( - "ERROR: Invalid ASCII string successfully constructed".into() - )), - err - ); + assert_eq!(ClarityTypeError::InvalidAsciiCharacter(1), err); } -/// The returned VMInternalError is consensus-critical. +// TODO: remove this comment. Is this truly consensus critical? i.e. if it just returns an error, does it matter if it +// maintains that its a rejectable block #[test] -fn test_utf8_data_to_value_returns_vm_internal_error_consensus_critical() { +fn test_utf8_data_to_value_returns_clarity_types_error_invalid_utf8_encoding_consensus_critical() { let err = UTF8Data::to_value(&vec![0xED, 0xA0, 0x80]).unwrap_err(); - assert_eq!( - VmExecutionError::from(VmInternalError::Expect( - "ERROR: Invalid UTF8 string successfully constructed".into() - )), - err - ); + assert_eq!(ClarityTypeError::InvalidUtf8Encoding, err); } -/// The returned VMInternalError is consensus-critical. +// TODO: remove this comment. Is this truly consensus critical? i.e. if it just returns an error, does it matter if it +// maintains that its a rejectable block? Currently even without my own changes, calls to from_data_typed are already +// immediately remapped #[test] -fn test_tuple_data_from_data_typed_returns_vm_internal_error_consensus_critical() { +fn test_tuple_data_from_data_typed_returns_clarity_type_error() { let tuple_type = TupleTypeSignature::try_from(vec![("a".into(), TypeSignature::IntType)]).unwrap(); let err = TupleData::from_data_typed( @@ -398,193 +385,237 @@ fn test_tuple_data_from_data_typed_returns_vm_internal_error_consensus_critical( ) .unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::FailureConstructingTupleWithType), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::IntType), + Box::new(Value::UInt(1)), + ), err ); } #[rstest] -#[case::not_a_string(Value::none(), VmInternalError::Expect("Expected ASCII string".to_string()))] -#[case::invalid_utf8(Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data: vec![0xED, 0xA0, 0x80] }))), VmInternalError::Expect("Non UTF-8 data in string".to_string()))] -fn test_value_expect_ascii_returns_vm_internal_error( +#[case::not_a_string( + Value::none(), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::STRING_ASCII_MIN), + Box::new(Value::none()) + ) +)] +#[case::invalid_utf8(Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data: vec![0xED, 0xA0, 0x80] }))), ClarityTypeError::InvalidUtf8Encoding)] +fn test_value_expect_ascii_returns_clarity_type_error( #[case] value: Value, - #[case] expected_err: VmInternalError, + #[case] expected_err: ClarityTypeError, ) { let err = value.expect_ascii().unwrap_err(); - assert_eq!(VmExecutionError::from(expected_err), err); + assert_eq!(expected_err, err); } -/// The returned VMInternalError is consensus-critical. +// TODO: remove this comment. Is this truly consensus critical? i.e. if it just returns an error, does it matter if it +// maintains that its a rejectable block? I think its up to the caller to determine if its consensus critical issue #[test] -fn test_value_expect_u128_returns_vm_internal_error_consensus_critical() { +fn test_value_expect_u128_returns_clarity_type_error() { let err = Value::none().expect_u128().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected u128".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::UIntType), + Box::new(Value::none()) + ), err ); } #[test] -fn test_value_expect_i128_returns_vm_internal_error() { +fn test_value_expect_i128_returns_clarity_type_error() { let err = Value::none().expect_i128().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected i128".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::IntType), + Box::new(Value::none()) + ), err ); } #[rstest] -#[case::not_a_buffer(Value::none(), VmInternalError::Expect("Expected buff".to_string()))] -#[case::too_small(Value::buff_from(vec![1, 2, 3, 4]).unwrap(), VmInternalError::Expect("Unexpected buff length".to_string()))] -fn test_value_expect_buff_returns_vm_internal_error( +#[case::not_a_buffer( + Value::none(), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::BUFFER_MIN), + Box::new(Value::none()) + ) +)] +#[case::too_small(Value::buff_from(vec![1, 2, 3, 4]).unwrap(), ClarityTypeError::ValueOutOfBounds)] +fn test_value_expect_buff_returns_clarity_type_error( #[case] value: Value, - #[case] expected_err: VmInternalError, + #[case] expected_err: ClarityTypeError, ) { let err = value.expect_buff(1).unwrap_err(); - assert_eq!(VmExecutionError::from(expected_err), err); + assert_eq!(expected_err, err); } #[test] -fn test_value_expect_tuple_returns_vm_internal_error() { +fn test_value_expect_tuple_returns_clarity_type_error() { let err = Value::none().expect_tuple().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected tuple".to_string())), + ClarityTypeError::TypeMismatchValue( + // Unfortunately cannot construct an empty Tuple type + // And to add it now would be intrusive. + Box::new(TypeSignature::NoType), + Box::new(Value::none()), + ), err ); } #[test] -fn test_value_expect_list_returns_vm_internal_error() { +fn test_value_expect_list_returns_clarity_type_error() { let err = Value::none().expect_list().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected list".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::SequenceType(SequenceSubtype::ListType( + TypeSignature::empty_list(), + ))), + Box::new(Value::none()), + ), err ); } #[test] -fn test_value_expect_buff_padded_returns_vm_internal_error() { +fn test_value_expect_buff_padded_returns_clarity_type_error() { let err = Value::none().expect_buff_padded(10, 0).unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected buff".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::BUFFER_MIN), + Box::new(Value::none()), + ), err ); } #[test] -fn test_value_expect_bool_returns_vm_internal_error() { +fn test_value_expect_bool_returns_clarity_type_error() { let err = Value::none().expect_bool().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected bool".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::BoolType), + Box::new(Value::none()), + ), err ); } -/// The returned VMInternalError is consensus-critical. +/// TODO: remove this comment. Is this really consensus critical? +/// I think its up to the caller to determine if its consensus critical issue #[test] -fn test_value_expect_optional_returns_vm_internal_error_consensus_critical() { +fn test_value_expect_optional_returns_clarity_type_error() { let err = Value::okay_true().expect_optional().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected optional".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::OptionalType(Box::new(TypeSignature::NoType))), + Box::new(Value::okay_true()), + ), err ); } -/// The returned VMInternalError is consensus-critical. +/// TODO: remove this comment. Is this really consensus critical? +/// I think its up to the caller to determine if its consensus critical issue #[test] -fn test_value_expect_principal_returns_vm_internal_error_consensus_critical() { +fn test_value_expect_principal_returns_clarity_type_error() { let err = Value::none().expect_principal().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected principal".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::PrincipalType), + Box::new(Value::none()), + ), err ); } -/// The returned VMInternalError is consensus-critical. +/// TODO: remove this comment. Is this really consensus critical? +/// I think its up to the caller to determine if its consensus critical issue #[test] -fn test_value_expect_callable_returns_vm_internal_error_consensus_critical() { +fn test_value_expect_callable_returns_clarity_type_error() { let err = Value::none().expect_callable().unwrap_err(); + // Unfortunately cannot construct an empty Callable type + // And to add it now would be intrusive. assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected callable".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::NoType), + Box::new(Value::none()), + ), err ); } #[test] -fn test_value_expect_result_returns_vm_internal_error() { +fn test_value_expect_result_returns_clarity_type_error() { let err = Value::none().expect_result().unwrap_err(); assert_eq!( - VmExecutionError::from(VmInternalError::Expect("Expected response".to_string())), + ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::ResponseType(Box::new(( + TypeSignature::NoType, + TypeSignature::NoType + )))), + Box::new(Value::none()), + ), err ); } #[rstest] -#[case::not_a_response(Value::none(), VmInternalError::Expect("Expected response".to_string()))] -#[case::not_an_ok_response(Value::error(Value::Int(1)).unwrap(), VmInternalError::Expect("Expected ok response".to_string()))] -fn test_value_expect_result_ok_returns_vm_internal_error( +#[case::not_a_response(Value::none(), ClarityTypeError::TypeMismatchValue(Box::new(TypeSignature::ResponseType(Box::new((TypeSignature::NoType, TypeSignature::NoType)))), Box::new(Value::none())))] +#[case::not_an_ok_response(Value::error(Value::Int(1)).unwrap(), ClarityTypeError::ResponseTypeMismatch { expected_ok: true, data_committed: false })] +fn test_value_expect_result_ok_returns_clarity_type_error( #[case] value: Value, - #[case] expected_err: VmInternalError, + #[case] expected_err: ClarityTypeError, ) { let err = value.expect_result_ok().unwrap_err(); - assert_eq!(VmExecutionError::from(expected_err), err); + assert_eq!(expected_err, err); } #[rstest] -#[case::not_a_response(Value::none(), VmInternalError::Expect("Expected response".to_string()))] -#[case::not_an_err_response(Value::okay_true(), VmInternalError::Expect("Expected err response".to_string()))] -fn test_value_expect_result_err_returns_vm_internal_error( +#[case::not_a_response(Value::none(), ClarityTypeError::TypeMismatchValue(Box::new(TypeSignature::ResponseType(Box::new((TypeSignature::NoType, TypeSignature::NoType)))), Box::new(Value::none())))] +#[case::not_an_err_response(Value::okay_true(), ClarityTypeError::ResponseTypeMismatch { expected_ok: false, data_committed: true })] +fn test_value_expect_result_err_returns_clarity_type_error( #[case] value: Value, - #[case] expected_err: VmInternalError, + #[case] expected_err: ClarityTypeError, ) { let err = value.expect_result_err().unwrap_err(); - assert_eq!(VmExecutionError::from(expected_err), err); + assert_eq!(expected_err, err); } -/// The returned VMInternalError is consensus-critical. +// TODO: remove this comment. Is this truly consensus critical? i.e. if it just returns an error, does it matter if it +// maintains that its a rejectable block #[test] -fn test_buff_data_len_returns_vm_internal_error_consensus_critical() { +fn test_buff_data_len_returns_clarity_type_error() { let err = BuffData { data: vec![1; MAX_VALUE_SIZE as usize + 1], } .len() .unwrap_err(); - assert_eq!( - VmExecutionError::from(VmInternalError::Expect( - "Data length should be valid".into() - )), - err - ); + assert_eq!(ClarityTypeError::ValueTooLarge, err); } #[test] -fn test_ascii_data_len_returns_vm_internal_error() { +fn test_ascii_data_len_returns_clarity_type_error() { let err = ASCIIData { data: vec![1; MAX_VALUE_SIZE as usize + 1], } .len() .unwrap_err(); - assert_eq!( - VmExecutionError::from(VmInternalError::Expect( - "Data length should be valid".into() - )), - err - ); + assert_eq!(ClarityTypeError::ValueTooLarge, err); } #[test] -fn test_utf8_data_len_returns_vm_internal_error() { +fn test_utf8_data_len_returns_clarity_type_error() { let err = UTF8Data { data: vec![vec![]; MAX_VALUE_SIZE as usize + 1], } .len() .unwrap_err(); - assert_eq!( - VmExecutionError::from(VmInternalError::Expect( - "Data length should be valid".into() - )), - err - ); + assert_eq!(ClarityTypeError::ValueTooLarge, err); } #[test] @@ -595,10 +626,7 @@ fn invalid_utf8_encoding_from_oob_unicode_escape() { let bad_utf8_literal = "\\u{110000}".to_string(); let err = Value::string_utf8_from_string_utf8_literal(bad_utf8_literal).unwrap_err(); - assert!(matches!( - err, - VmExecutionError::Unchecked(CheckErrorKind::InvalidUTF8Encoding) - )); + assert!(matches!(err, ClarityTypeError::InvalidUtf8Encoding)); } #[test] @@ -611,10 +639,7 @@ fn invalid_string_ascii_from_bytes() { let err = Value::string_ascii_from_bytes(bad_bytes).unwrap_err(); - assert!(matches!( - err, - VmExecutionError::Unchecked(CheckErrorKind::InvalidCharactersDetected) - )); + assert!(matches!(err, ClarityTypeError::InvalidAsciiCharacter(_))); } #[test] @@ -624,8 +649,5 @@ fn invalid_utf8_string_from_bytes() { let err = Value::string_utf8_from_bytes(bad_bytes).unwrap_err(); - assert!(matches!( - err, - VmExecutionError::Unchecked(CheckErrorKind::InvalidCharactersDetected) - )); + assert!(matches!(err, ClarityTypeError::InvalidUtf8Encoding)); } diff --git a/clarity-types/src/tests/types/serialization.rs b/clarity-types/src/tests/types/serialization.rs index 9878bf148c..05df8194cc 100644 --- a/clarity-types/src/tests/types/serialization.rs +++ b/clarity-types/src/tests/types/serialization.rs @@ -14,12 +14,11 @@ // along with this program. If not, see . use std::io::Write; -use crate::VmExecutionError; -use crate::errors::{CheckErrorKind, VmInternalError}; use crate::types::serialization::SerializationError; use crate::types::{ - ASCIIData, CharType, MAX_VALUE_SIZE, PrincipalData, QualifiedContractIdentifier, SequenceData, - StandardPrincipalData, TupleData, TypeSignature, Value, + ASCIIData, CharType, ClarityTypeError, MAX_VALUE_SIZE, PrincipalData, + QualifiedContractIdentifier, SequenceData, StandardPrincipalData, TupleData, TypeSignature, + Value, }; fn test_deser_ser(v: Value) { @@ -374,7 +373,7 @@ fn try_deser_large_list() { assert_eq!( Value::try_deserialize_bytes_untyped(&buff).unwrap_err(), - SerializationError::DeserializationError("Illegal list type".to_string()) + SerializationError::DeserializationFailure("Illegal list type".to_string()) ); } @@ -386,7 +385,7 @@ fn try_deser_large_tuple() { assert_eq!( Value::try_deserialize_bytes_untyped(&buff).unwrap_err(), - SerializationError::DeserializationError("Illegal tuple type".to_string()) + SerializationError::DeserializationFailure("Illegal tuple type".to_string()) ); } @@ -394,7 +393,7 @@ fn try_deser_large_tuple() { fn try_overflow_stack() { let input = "08080808080808080808070707080807080808080808080708080808080708080707080707080807080808080808080708080808080708080707080708070807080808080808080708080808080708080708080808080808080807070807080808080808070808070707080807070808070808080808070808070708070807080808080808080707080708070807080708080808080808070808080808070808070808080808080808080707080708080808080807080807070708080707080807080808080807080807070807080708080808080808070708070808080808080708080707070808070708080807080807070708"; assert_eq!( - Err(CheckErrorKind::TypeSignatureTooDeep.into()), + Err(ClarityTypeError::TypeSignatureTooDeep.into()), Value::try_deserialize_hex_untyped(input) ); } @@ -416,32 +415,30 @@ fn test_principals() { test_bad_expectation(standard_p, TypeSignature::BoolType); } -/// The returned VmInternalError is consensus-critical. +/// TODO: remove this comment: I have made it so that any serialize_to_vec that fails is mapped to an Expect. +/// Not sure that is sufficient... #[test] -fn test_serialize_to_vec_returns_vm_internal_error_consensus_critical() { +fn test_serialize_to_vec_returns_serialization_failure() { 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!( - VmExecutionError::from(VmInternalError::Expect( - "IOError filling byte buffer.".into() - )), - err.into() + SerializationError::SerializationFailure(ClarityTypeError::ValueTooLarge.to_string()), + err ); } -/// The returned VmInternalError is consensus-critical. +/// TODO: remove this comment: I have made it so that any serialize_to_vec that fails is mapped to an Expect. +/// Not sure that is sufficient... #[test] -fn test_serialize_to_hex_returns_vm_internal_error_consensus_critical() { +fn test_serialize_to_hex_returns_serialization_failure() { 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!( - VmExecutionError::from(VmInternalError::Expect( - "IOError filling byte buffer.".into() - )), - err.into() + SerializationError::SerializationFailure(ClarityTypeError::ValueTooLarge.to_string()), + err ); } diff --git a/clarity-types/src/tests/types/signatures.rs b/clarity-types/src/tests/types/signatures.rs index a41dbf6ed3..1636220e9f 100644 --- a/clarity-types/src/tests/types/signatures.rs +++ b/clarity-types/src/tests/types/signatures.rs @@ -14,14 +14,14 @@ // along with this program. If not, see . use std::collections::HashSet; -use crate::errors::analysis::CommonCheckErrorKind; use crate::representations::CONTRACT_MAX_NAME_LENGTH; use crate::types::TypeSignature::{BoolType, IntType, ListUnionType, UIntType}; use crate::types::signatures::{CallableSubtype, TypeSignature}; use crate::types::{ - BufferLength, MAX_TO_ASCII_BUFFER_LEN, MAX_TO_ASCII_RESULT_LEN, MAX_TYPE_DEPTH, - MAX_UTF8_VALUE_SIZE, MAX_VALUE_SIZE, QualifiedContractIdentifier, SequenceSubtype, - StringSubtype, StringUTF8Length, TraitIdentifier, TupleTypeSignature, WRAPPER_VALUE_SIZE, + BufferLength, ClarityTypeError, MAX_TO_ASCII_BUFFER_LEN, MAX_TO_ASCII_RESULT_LEN, + MAX_TYPE_DEPTH, MAX_UTF8_VALUE_SIZE, MAX_VALUE_SIZE, QualifiedContractIdentifier, + SequenceSubtype, StringSubtype, StringUTF8Length, TraitIdentifier, TupleTypeSignature, + WRAPPER_VALUE_SIZE, }; #[test] @@ -43,7 +43,7 @@ fn test_buffer_length_try_from_u32_trait() { assert_eq!(MAX_VALUE_SIZE, buffer.get_value()); let err = BufferLength::try_from(MAX_VALUE_SIZE + 1).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueTooLarge, err); + assert_eq!(ClarityTypeError::ValueTooLarge, err); } #[test] @@ -55,7 +55,7 @@ fn test_buffer_length_try_from_usize_trait() { assert_eq!(MAX_VALUE_SIZE, buffer.get_value()); let err = BufferLength::try_from(MAX_VALUE_SIZE as usize + 1).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueTooLarge, err); + assert_eq!(ClarityTypeError::ValueTooLarge, err); } #[test] @@ -67,10 +67,10 @@ fn test_buffer_length_try_from_i128_trait() { assert_eq!(MAX_VALUE_SIZE, buffer.get_value()); let err = BufferLength::try_from(MAX_VALUE_SIZE as i128 + 1).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueTooLarge, err); + assert_eq!(ClarityTypeError::ValueTooLarge, err); let err = BufferLength::try_from(-1_i128).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueOutOfBounds, err); + assert_eq!(ClarityTypeError::ValueOutOfBounds, err); } #[test] @@ -229,7 +229,7 @@ fn test_string_utf8_length_try_from_u32_trait() { assert_eq!(MAX_UTF8_VALUE_SIZE, string.get_value()); let err = StringUTF8Length::try_from(MAX_UTF8_VALUE_SIZE + 1).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueTooLarge, err); + assert_eq!(ClarityTypeError::ValueTooLarge, err); } #[test] @@ -244,7 +244,7 @@ fn test_string_utf8_length_try_from_usize_trait() { assert_eq!(MAX_UTF8_VALUE_SIZE, string.get_value()); let err = StringUTF8Length::try_from(MAX_UTF8_VALUE_SIZE as usize + 1).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueTooLarge, err); + assert_eq!(ClarityTypeError::ValueTooLarge, err); } #[test] @@ -259,10 +259,10 @@ fn test_string_utf8_length_try_from_i128_trait() { assert_eq!(MAX_UTF8_VALUE_SIZE, string.get_value()); let err = StringUTF8Length::try_from(MAX_UTF8_VALUE_SIZE as i128 + 1).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueTooLarge, err); + assert_eq!(ClarityTypeError::ValueTooLarge, err); let err = StringUTF8Length::try_from(-1_i128).unwrap_err(); - assert_eq!(CommonCheckErrorKind::ValueOutOfBounds, err); + assert_eq!(ClarityTypeError::ValueOutOfBounds, err); } #[test] @@ -826,11 +826,11 @@ fn test_least_supertype() { for pair in bad_pairs { matches!( TypeSignature::least_supertype_v2_1(&pair.0, &pair.1).unwrap_err(), - CommonCheckErrorKind::TypeError(..) + ClarityTypeError::TypeMismatch(..) ); matches!( TypeSignature::least_supertype_v2_1(&pair.1, &pair.0).unwrap_err(), - CommonCheckErrorKind::TypeError(..) + ClarityTypeError::TypeMismatch(..) ); } } diff --git a/clarity-types/src/types/mod.rs b/clarity-types/src/types/mod.rs index 1ee7e2dfbb..3fc8ad50a5 100644 --- a/clarity-types/src/types/mod.rs +++ b/clarity-types/src/types/mod.rs @@ -16,6 +16,7 @@ pub mod serialization; pub mod signatures; +use core::error; use std::collections::BTreeMap; use std::collections::btree_map::Entry; use std::{char, fmt, str}; @@ -36,8 +37,8 @@ pub use self::signatures::{ AssetIdentifier, BufferLength, ListTypeData, SequenceSubtype, StringSubtype, StringUTF8Length, TupleTypeSignature, TypeSignature, }; -use crate::errors::analysis::CommonCheckErrorKind; -use crate::errors::{CheckErrorKind, RuntimeError, VmExecutionError, VmInternalError}; +use crate::VmExecutionError; +use crate::diagnostic::DiagnosableError; use crate::representations::{ClarityName, ContractName, SymbolicExpression}; /// Maximum size in bytes allowed for types. @@ -59,6 +60,170 @@ pub const MAX_TYPE_DEPTH: u8 = 32; /// this is the charged size for wrapped values, i.e., response or optionals pub const WRAPPER_VALUE_SIZE: u32 = 1; +/// Errors originating purely from the Clarity type system layer. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ClarityTypeError { + // Size & Depth Invariants + /// The constructed value exceeds the maximum allowed Clarity value size. + ValueTooLarge, + /// The constructed value exceeds the maximum allowed nesting depth. + TypeSignatureTooDeep, + + // String & Encoding Errors + /// A non-ASCII byte was found in an ASCII string. + InvalidAsciiCharacter(u8), + /// The provided bytes did not form valid UTF-8. + InvalidUtf8Encoding, + + // List, Tuple, & Structural Type Errors + /// A list operation failed because element types do not match. + ListTypeMismatch, + /// An index was out of bounds for a sequence. + ValueOutOfBounds, + /// A tuple was constructed with duplicate field names. + DuplicateTupleField(String), + /// Referenced tuple field does not exist in the tuple type. + /// The `String` wraps the requested field name, and the `TupleTypeSignature` wraps the tuple’s type. + NoSuchTupleField(String, TupleTypeSignature), + /// Value does not match the expected type. + /// The `Box` wraps the expected type, and the `Box` wraps the invalid value. + TypeMismatchValue(Box, Box), + /// Expected type does not match the actual type during analysis. + /// The first `Box` wraps the expected type, and the second wraps the actual type. + TypeMismatch(Box, Box), + /// Expected an different response type + ResponseTypeMismatch { + /// Whether the response type should be an `Ok` response + expected_ok: bool, + /// Whether the response data was committed or not + data_committed: bool, + }, + /// Invalid contract name. + /// The `String` represents the offending value. + InvalidContractName(String), + /// Invalid Clarity name. + /// The `String` represents the offending value. + InvalidClarityName(String), + /// Invalid URL. + /// The `String` represents the offending value. + InvalidUrlString(String), + /// Empty tuple is not allowed in Clarity. + EmptyTuplesNotAllowed, + /// Supertype (e.g., trait or union) exceeds the maximum allowed size or complexity. + /// This error indicates a transaction would invalidate a block if included. + SupertypeTooLarge, + /// Type description is invalid or malformed, preventing proper type-checking. + InvalidTypeDescription, + /// Sequence element length mismatch + SequenceElementArityMismatch { expected: usize, found: usize }, + /// Expected a sequence value + ExpectedSequenceValue, + + // Principal & Identifier Errors + /// An invalid version byte was used for a principal. + InvalidPrincipalVersion(u8), + /// An invalid principal byte length was supplied. + InvalidPrincipalLength(usize), + /// C32 decode failed + InvalidPrincipalEncoding(String), + /// An invalid qualified identifier was supplied with a missing '.' separator. + QualifiedContractMissingDot, + /// An invalid qualified identifier was supplied with a missing issuer. + QualifiedContractEmptyIssuer, + + // Type Resolution & Abstract Type Failures + /// The value has a valid abstract type, but it cannot be serialized + /// into a concrete consensus representation. + CouldNotDetermineSerializationType, + /// The type signature could not be determined. + CouldNotDetermineType, + + /// Type is unsupported in the given epoch + UnsupportedTypeInEpoch(Box, StacksEpochId), + /// Unsupported epoch + UnsupportedEpoch(StacksEpochId), + /// Something unexpected happened that should not be possible + InvariantViolation(String), +} + +impl fmt::Display for ClarityTypeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl DiagnosableError for ClarityTypeError { + fn message(&self) -> String { + match &self { + // Size & Depth + Self::ValueTooLarge => "value exceeds maximum Clarity size".into(), + Self::TypeSignatureTooDeep => "type signature exceeds maximum nesting depth".into(), + // Encoding + Self::InvalidAsciiCharacter(b) => format!("invalid ASCII character byte: 0x{b:02x}"), + Self::InvalidUtf8Encoding => "invalid UTF-8 encoding".into(), + // List / Tuple / Type Construction + Self::ListTypeMismatch => "list elements do not match required type".into(), + Self::ValueOutOfBounds => "value index is out of bounds".into(), + Self::DuplicateTupleField(name) => format!("duplicate tuple field '{name}'"), + Self::NoSuchTupleField(field_name, tuple_signature) => { + format!("cannot find field '{field_name}' in tuple '{tuple_signature}'") + } + Self::TypeMismatchValue(expected_type, found_value) => { + format!("expecting expression of type '{expected_type}', found '{found_value}'") + } + Self::TypeMismatch(expected_type, found_type) => { + format!("expecting expression of type '{expected_type}', found '{found_type}'") + } + Self::ResponseTypeMismatch { + expected_ok, + data_committed, + } => format!( + "expected ok response `{expected_ok}`, found data committed `{data_committed}`" + ), + Self::InvalidClarityName(value) => format!("invalid clarity name `{value}`"), + Self::InvalidContractName(value) => format!("invalid contract name `{value}`"), + Self::InvalidUrlString(value) => format!("invalid URL string `{value}`"), + Self::EmptyTuplesNotAllowed => "tuple types may not be empty".into(), + Self::SupertypeTooLarge => "supertype of two types is too large".into(), + Self::InvalidTypeDescription => "supplied type description is invalid".into(), + Self::SequenceElementArityMismatch { expected, found } => { + format!("sequence expected {expected} elements, but found {found} elements") + } + Self::ExpectedSequenceValue => "expected sequence value".into(), + // Principal + Self::InvalidPrincipalVersion(v) => format!("invalid principal version byte: {v}"), + Self::InvalidPrincipalLength(len) => { + format!("invalid principal byte length. Expected 20 bytes. Got: {len}") + } + Self::InvalidPrincipalEncoding(msg) => format!("invalid principal encoding: {msg}"), + Self::QualifiedContractMissingDot => { + "expected a `.` in a qualified contract name".into() + } + Self::QualifiedContractEmptyIssuer => "Expected an issuer, but found none".into(), + // Type resolution + Self::CouldNotDetermineSerializationType => { + "could not determine the input type for the serialization function".into() + } + Self::CouldNotDetermineType => "could not determine the input type".into(), + Self::UnsupportedTypeInEpoch(type_signature, epoch) => { + format!("{type_signature} is unsupported in {epoch}") + } + Self::UnsupportedEpoch(epoch) => format!("{epoch} is unsupported"), + Self::InvariantViolation(msg) => format!("Invariant violation: {msg}"), + } + } + + fn suggestion(&self) -> Option { + None + } +} + +impl error::Error for ClarityTypeError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + None + } +} + #[derive(Debug, Clone, Eq, Serialize, Deserialize)] pub struct TupleData { // todo: remove type_signature @@ -91,9 +256,9 @@ impl StandardPrincipalData { } impl StandardPrincipalData { - pub fn new(version: u8, bytes: [u8; 20]) -> std::result::Result { + pub fn new(version: u8, bytes: [u8; 20]) -> Result { if version >= 32 { - return Err(VmInternalError::Expect("Unexpected principal data".into())); + return Err(ClarityTypeError::InvalidPrincipalVersion(version)); } Ok(Self(version, bytes)) } @@ -175,7 +340,7 @@ impl QualifiedContractIdentifier { Self { issuer, name } } - pub fn local(name: &str) -> Result { + pub fn local(name: &str) -> Result { let name = name.to_string().try_into()?; Ok(Self::new(StandardPrincipalData::transient(), name)) } @@ -194,14 +359,10 @@ impl QualifiedContractIdentifier { self.issuer.1 == [0; 20] } - pub fn parse(literal: &str) -> Result { + pub fn parse(literal: &str) -> Result { let split: Vec<_> = literal.splitn(2, '.').collect(); if split.len() != 2 { - return Err(RuntimeError::TypeParseFailure( - "Invalid principal literal: expected a `.` in a qualified contract name" - .to_string(), - ) - .into()); + return Err(ClarityTypeError::QualifiedContractMissingDot); } let sender = PrincipalData::parse_standard_principal(split[0])?; let name = split[1].to_string().try_into()?; @@ -283,29 +444,25 @@ impl TraitIdentifier { } } - pub fn parse_fully_qualified(literal: &str) -> Result { + pub fn parse_fully_qualified(literal: &str) -> Result { let (issuer, contract_name, name) = Self::parse(literal)?; - let issuer = issuer.ok_or(RuntimeError::BadTypeConstruction)?; + let issuer = issuer.ok_or(ClarityTypeError::QualifiedContractEmptyIssuer)?; Ok(TraitIdentifier::new(issuer, contract_name, name)) } pub fn parse_sugared_syntax( literal: &str, - ) -> Result<(ContractName, ClarityName), VmExecutionError> { + ) -> Result<(ContractName, ClarityName), ClarityTypeError> { let (_, contract_name, name) = Self::parse(literal)?; Ok((contract_name, name)) } pub fn parse( literal: &str, - ) -> Result<(Option, ContractName, ClarityName), VmExecutionError> { + ) -> Result<(Option, ContractName, ClarityName), ClarityTypeError> { let split: Vec<_> = literal.splitn(3, '.').collect(); if split.len() != 3 { - return Err(RuntimeError::TypeParseFailure( - "Invalid principal literal: expected a `.` in a qualified contract name" - .to_string(), - ) - .into()); + return Err(ClarityTypeError::QualifiedContractMissingDot); } let issuer = match split[0].len() { @@ -343,7 +500,16 @@ pub enum SequenceData { } impl SequenceData { - pub fn atom_values(&mut self) -> Result, VmExecutionError> { + pub fn type_signature(&self) -> Result { + match self { + SequenceData::Buffer(b) => b.type_signature(), + SequenceData::List(l) => l.type_signature(), + SequenceData::String(CharType::ASCII(a)) => a.type_signature(), + SequenceData::String(CharType::UTF8(u)) => u.type_signature(), + } + } + + pub fn atom_values(&mut self) -> Result, ClarityTypeError> { match self { SequenceData::Buffer(data) => data.atom_values(), SequenceData::List(data) => data.atom_values(), @@ -352,7 +518,7 @@ impl SequenceData { } } - pub fn element_size(&self) -> Result { + pub fn element_size(&self) -> Result { let out = match self { SequenceData::Buffer(..) => TypeSignature::BUFFER_MIN.size(), SequenceData::List(data) => data.type_signature.get_list_item_type().size(), @@ -375,7 +541,7 @@ impl SequenceData { self.len() == 0 } - pub fn element_at(self, index: usize) -> Result, VmExecutionError> { + pub fn element_at(self, index: usize) -> Result, ClarityTypeError> { if self.len() <= index { return Ok(None); } @@ -383,11 +549,7 @@ impl SequenceData { SequenceData::Buffer(data) => Value::buff_from_byte(data.data[index]), SequenceData::List(mut data) => data.data.remove(index), SequenceData::String(CharType::ASCII(data)) => { - Value::string_ascii_from_bytes(vec![data.data[index]]).map_err(|_| { - VmInternalError::Expect( - "BUG: failed to initialize single-byte ASCII buffer".into(), - ) - })? + Value::string_ascii_from_bytes(vec![data.data[index]])? } SequenceData::String(CharType::UTF8(mut data)) => { Value::Sequence(SequenceData::String(CharType::UTF8(UTF8Data { @@ -404,7 +566,7 @@ impl SequenceData { epoch: &StacksEpochId, index: usize, element: Value, - ) -> Result { + ) -> Result { let seq_length = self.len(); // Check that the length of the provided element is 1. In the case that SequenceData @@ -413,14 +575,17 @@ impl SequenceData { if let Value::Sequence(data) = &element { let elem_length = data.len(); if elem_length != 1 { - return Err(RuntimeError::BadTypeConstruction.into()); + return Err(ClarityTypeError::SequenceElementArityMismatch { + expected: 1, + found: elem_length, + }); } } else { - return Err(RuntimeError::BadTypeConstruction.into()); + return Err(ClarityTypeError::ExpectedSequenceValue); } } if index >= seq_length { - return Err(CheckErrorKind::ValueOutOfBounds.into()); + return Err(ClarityTypeError::ValueOutOfBounds); } let new_seq_data = match (self, element) { @@ -431,7 +596,7 @@ impl SequenceData { (SequenceData::List(mut data), elem) => { let entry_type = data.type_signature.get_list_item_type(); if !entry_type.admits(epoch, &elem)? { - return Err(CheckErrorKind::ListTypesMustMatch.into()); + return Err(ClarityTypeError::ListTypeMismatch); } data.data[index] = elem; SequenceData::List(data) @@ -450,13 +615,18 @@ impl SequenceData { data.data[index] = elem.data.swap_remove(0); SequenceData::String(CharType::UTF8(data)) } - _ => return Err(CheckErrorKind::ListTypesMustMatch.into()), + (seq, element) => { + return Err(ClarityTypeError::TypeMismatchValue( + Box::new(seq.type_signature()?), + Box::new(element), + )); + } }; Value::some(Value::Sequence(new_seq_data)) } - pub fn contains(&self, to_find: Value) -> Result, VmExecutionError> { + pub fn contains(&self, to_find: Value) -> Result, ClarityTypeError> { match self { SequenceData::Buffer(data) => { if let Value::Sequence(SequenceData::Buffer(to_find_vec)) = to_find { @@ -471,11 +641,10 @@ impl SequenceData { Ok(None) } } else { - Err(CheckErrorKind::TypeValueError( + Err(ClarityTypeError::TypeMismatchValue( Box::new(TypeSignature::BUFFER_MIN), Box::new(to_find), - ) - .into()) + )) } } SequenceData::List(data) => { @@ -500,11 +669,10 @@ impl SequenceData { Ok(None) } } else { - Err(CheckErrorKind::TypeValueError( + Err(ClarityTypeError::TypeMismatchValue( Box::new(TypeSignature::STRING_ASCII_MIN), Box::new(to_find), - ) - .into()) + )) } } SequenceData::String(CharType::UTF8(data)) => { @@ -521,11 +689,10 @@ impl SequenceData { Ok(None) } } else { - Err(CheckErrorKind::TypeValueError( + Err(ClarityTypeError::TypeMismatchValue( Box::new(TypeSignature::STRING_UTF8_MIN), Box::new(to_find), - ) - .into()) + )) } } } @@ -578,7 +745,7 @@ impl SequenceData { &mut self, epoch: &StacksEpochId, other_seq: SequenceData, - ) -> Result<(), VmExecutionError> { + ) -> Result<(), ClarityTypeError> { match (self, other_seq) { (SequenceData::List(inner_data), SequenceData::List(other_inner_data)) => { inner_data.append(epoch, other_inner_data)?; @@ -594,7 +761,12 @@ impl SequenceData { SequenceData::String(CharType::UTF8(inner_data)), SequenceData::String(CharType::UTF8(ref mut other_inner_data)), ) => inner_data.append(other_inner_data), - _ => return Err(RuntimeError::BadTypeConstruction.into()), + (seq, other_seq) => { + return Err(ClarityTypeError::TypeMismatch( + Box::new(seq.type_signature()?), + Box::new(other_seq.type_signature()?), + )); + } }; Ok(()) } @@ -604,7 +776,7 @@ impl SequenceData { epoch: &StacksEpochId, left_position: usize, right_position: usize, - ) -> Result { + ) -> Result { let empty_seq = left_position == right_position; let result = match self { @@ -712,15 +884,15 @@ impl fmt::Display for UTF8Data { } pub trait SequencedValue { - fn type_signature(&self) -> std::result::Result; + fn type_signature(&self) -> std::result::Result; fn items(&self) -> &Vec; fn drained_items(&mut self) -> Vec; - fn to_value(v: &T) -> Result; + fn to_value(v: &T) -> Result; - fn atom_values(&mut self) -> Result, VmExecutionError> { + fn atom_values(&mut self) -> Result, ClarityTypeError> { self.drained_items() .iter() .map(|item| Ok(SymbolicExpression::atom_value(Self::to_value(item)?))) @@ -737,13 +909,13 @@ impl SequencedValue for ListData { self.data.drain(..).collect() } - fn type_signature(&self) -> std::result::Result { + fn type_signature(&self) -> std::result::Result { Ok(TypeSignature::SequenceType(SequenceSubtype::ListType( self.type_signature.clone(), ))) } - fn to_value(v: &Value) -> Result { + fn to_value(v: &Value) -> Result { Ok(v.clone()) } } @@ -757,18 +929,14 @@ impl SequencedValue for BuffData { self.data.drain(..).collect() } - fn type_signature(&self) -> std::result::Result { - let buff_length = BufferLength::try_from(self.data.len()).map_err(|_| { - CommonCheckErrorKind::Expects( - "ERROR: Too large of a buffer successfully constructed.".into(), - ) - })?; + fn type_signature(&self) -> Result { + let buff_length = BufferLength::try_from(self.data.len())?; Ok(TypeSignature::SequenceType(SequenceSubtype::BufferType( buff_length, ))) } - fn to_value(v: &u8) -> Result { + fn to_value(v: &u8) -> Result { Ok(Value::buff_from_byte(*v)) } } @@ -782,22 +950,15 @@ impl SequencedValue for ASCIIData { self.data.drain(..).collect() } - fn type_signature(&self) -> std::result::Result { - let buff_length = BufferLength::try_from(self.data.len()).map_err(|_| { - CommonCheckErrorKind::Expects( - "ERROR: Too large of a buffer successfully constructed.".into(), - ) - })?; + fn type_signature(&self) -> std::result::Result { + let buff_length = BufferLength::try_from(self.data.len())?; Ok(TypeSignature::SequenceType(SequenceSubtype::StringType( StringSubtype::ASCII(buff_length), ))) } - fn to_value(v: &u8) -> Result { - Value::string_ascii_from_bytes(vec![*v]).map_err(|_| { - VmInternalError::Expect("ERROR: Invalid ASCII string successfully constructed".into()) - .into() - }) + fn to_value(v: &u8) -> Result { + Value::string_ascii_from_bytes(vec![*v]) } } @@ -810,40 +971,30 @@ impl SequencedValue> for UTF8Data { self.data.drain(..).collect() } - fn type_signature(&self) -> std::result::Result { - let str_len = StringUTF8Length::try_from(self.data.len()).map_err(|_| { - CommonCheckErrorKind::Expects( - "ERROR: Too large of a buffer successfully constructed.".into(), - ) - })?; + fn type_signature(&self) -> std::result::Result { + let str_len = StringUTF8Length::try_from(self.data.len())?; Ok(TypeSignature::SequenceType(SequenceSubtype::StringType( StringSubtype::UTF8(str_len), ))) } - fn to_value(v: &Vec) -> Result { - Value::string_utf8_from_bytes(v.clone()).map_err(|_| { - VmInternalError::Expect("ERROR: Invalid UTF8 string successfully constructed".into()) - .into() - }) + fn to_value(v: &Vec) -> Result { + Value::string_utf8_from_bytes(v.clone()) } } impl OptionalData { - pub fn type_signature(&self) -> std::result::Result { - let type_result = match self.data { + pub fn type_signature(&self) -> Result { + match self.data { Some(ref v) => TypeSignature::new_option(TypeSignature::type_of(v)?), None => TypeSignature::new_option(TypeSignature::NoType), - }; - type_result.map_err(|_| { - CommonCheckErrorKind::Expects("Should not have constructed too large of a type.".into()) - }) + } } } impl ResponseData { - pub fn type_signature(&self) -> std::result::Result { - let type_result = match self.committed { + pub fn type_signature(&self) -> Result { + match self.committed { true => TypeSignature::new_response( TypeSignature::type_of(&self.data)?, TypeSignature::NoType, @@ -852,10 +1003,7 @@ impl ResponseData { TypeSignature::NoType, TypeSignature::type_of(&self.data)?, ), - }; - type_result.map_err(|_| { - CommonCheckErrorKind::Expects("Should not have constructed too large of a type.".into()) - }) + } } } @@ -874,11 +1022,11 @@ impl PartialEq for TupleData { pub const NONE: Value = Value::Optional(OptionalData { data: None }); impl Value { - pub fn some(data: Value) -> Result { + pub fn some(data: Value) -> Result { if data.size()? + WRAPPER_VALUE_SIZE > MAX_VALUE_SIZE { - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) } else if data.depth()? + 1 > MAX_TYPE_DEPTH { - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) } else { Ok(Value::Optional(OptionalData { data: Some(Box::new(data)), @@ -911,11 +1059,11 @@ impl Value { }) } - pub fn okay(data: Value) -> Result { + pub fn okay(data: Value) -> Result { if data.size()? + WRAPPER_VALUE_SIZE > MAX_VALUE_SIZE { - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) } else if data.depth()? + 1 > MAX_TYPE_DEPTH { - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) } else { Ok(Value::Response(ResponseData { committed: true, @@ -924,11 +1072,11 @@ impl Value { } } - pub fn error(data: Value) -> Result { + pub fn error(data: Value) -> Result { if data.size()? + WRAPPER_VALUE_SIZE > MAX_VALUE_SIZE { - Err(CheckErrorKind::ValueTooLarge.into()) + Err(ClarityTypeError::ValueTooLarge) } else if data.depth()? + 1 > MAX_TYPE_DEPTH { - Err(CheckErrorKind::TypeSignatureTooDeep.into()) + Err(ClarityTypeError::TypeSignatureTooDeep) } else { Ok(Value::Response(ResponseData { committed: false, @@ -937,35 +1085,36 @@ impl Value { } } - pub fn size(&self) -> Result { - Ok(TypeSignature::type_of(self)?.size()?) + pub fn size(&self) -> Result { + TypeSignature::type_of(self)?.size() } - pub fn depth(&self) -> Result { + pub fn depth(&self) -> Result { Ok(TypeSignature::type_of(self)?.depth()) } - /// Invariant: the supplied Values have already been "checked", i.e., it's a valid Value object - /// this invariant is enforced through the Value constructors, each of which checks to ensure - /// that any typing data is correct. + // TODO: remove this comment. This is to help reviewers: list_with_type is only called in + // serialization.rs where its returned error is immediately ignored. Therefore changes to the error + // types in here are not consensus-breaking pub fn list_with_type( epoch: &StacksEpochId, list_data: Vec, expected_type: ListTypeData, - ) -> Result { - // Constructors for TypeSignature ensure that the size of the Value cannot - // be greater than MAX_VALUE_SIZE (they error on such constructions) - // so we do not need to perform that check here. + ) -> Result { if (expected_type.get_max_len() as usize) < list_data.len() { - return Err(VmInternalError::FailureConstructingListWithType.into()); + return Err(ClarityTypeError::ValueTooLarge); } { let expected_item_type = expected_type.get_list_item_type(); for item in &list_data { - if !expected_item_type.admits(epoch, item)? { - return Err(VmInternalError::FailureConstructingListWithType.into()); + let admits = expected_item_type + .admits(epoch, item) + .map_err(|_| ClarityTypeError::ListTypeMismatch)?; + + if !admits { + return Err(ClarityTypeError::ListTypeMismatch); } } } @@ -976,7 +1125,7 @@ impl Value { }))) } - pub fn cons_list_unsanitized(list_data: Vec) -> Result { + pub fn cons_list_unsanitized(list_data: Vec) -> Result { let type_sig = TypeSignature::construct_parent_list_type(&list_data)?; Ok(Value::Sequence(SequenceData::List(ListData { data: list_data, @@ -985,14 +1134,14 @@ impl Value { } #[cfg(any(test, feature = "testing"))] - pub fn list_from(list_data: Vec) -> Result { + pub fn list_from(list_data: Vec) -> Result { Value::cons_list_unsanitized(list_data) } pub fn cons_list( list_data: Vec, epoch: &StacksEpochId, - ) -> Result { + ) -> Result { // Constructors for TypeSignature ensure that the size of the Value cannot // be greater than MAX_VALUE_SIZE (they error on such constructions) // Aaron: at this point, we've _already_ allocated memory for this type. @@ -1007,7 +1156,7 @@ impl Value { .map(|(value, _did_sanitize)| value) }) .collect(); - let list_data = list_data_opt.ok_or_else(|| CheckErrorKind::ListTypesMustMatch)?; + let list_data = list_data_opt.ok_or_else(|| ClarityTypeError::ListTypeMismatch)?; Ok(Value::Sequence(SequenceData::List(ListData { data: list_data, type_signature: type_sig, @@ -1015,8 +1164,8 @@ impl Value { } /// # Errors - /// - CheckErrorKind::ValueTooLarge if `buff_data` is too large. - pub fn buff_from(buff_data: Vec) -> Result { + /// - ClarityTypeError::ValueTooLarge if `buff_data` is too large. + pub fn buff_from(buff_data: Vec) -> Result { // check the buffer size BufferLength::try_from(buff_data.len())?; // construct the buffer @@ -1029,13 +1178,13 @@ impl Value { Value::Sequence(SequenceData::Buffer(BuffData { data: vec![byte] })) } - pub fn string_ascii_from_bytes(bytes: Vec) -> Result { + 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(CheckErrorKind::InvalidCharactersDetected.into()); + return Err(ClarityTypeError::InvalidAsciiCharacter(*b)); } } // construct the string @@ -1044,26 +1193,28 @@ impl Value { )))) } + // This is parsing escaped clarity literals and is essentially part of the lexer pub fn string_utf8_from_string_utf8_literal( tokenized_str: String, - ) -> Result { + ) -> Result { let wrapped_codepoints_matcher = Regex::new("^\\\\u\\{(?P[[:xdigit:]]+)\\}") - .map_err(|_| VmInternalError::Expect("Bad regex".into()))?; + .map_err(|_| ClarityTypeError::InvariantViolation("Bad regex".into()))?; 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") - .ok_or_else(|| VmInternalError::Expect("Expected capture".into()))?; + let matched = captures.name("value").ok_or_else(|| { + ClarityTypeError::InvariantViolation("Expected capture".into()) + })?; let scalar_value = window[matched.start()..matched.end()].to_string(); let unicode_char = { // This first InvalidUTF8Encoding is logically unreachable: the escape regex rejects non-hex digits, // so from_str_radix only sees valid hex and never errors here. let u = u32::from_str_radix(&scalar_value, 16) - .map_err(|_| CheckErrorKind::InvalidUTF8Encoding)?; - let c = char::from_u32(u).ok_or_else(|| CheckErrorKind::InvalidUTF8Encoding)?; + .map_err(|_| ClarityTypeError::InvalidUtf8Encoding)?; + let c = + char::from_u32(u).ok_or_else(|| ClarityTypeError::InvalidUtf8Encoding)?; let mut encoded_char: Vec = vec![0; c.len_utf8()]; c.encode_utf8(&mut encoded_char[..]); encoded_char @@ -1087,11 +1238,11 @@ impl Value { )))) } - pub fn string_utf8_from_bytes(bytes: Vec) -> Result { - let validated_utf8_str = match str::from_utf8(&bytes) { - Ok(string) => string, - _ => return Err(CheckErrorKind::InvalidCharactersDetected.into()), - }; + pub fn string_utf8_from_bytes(bytes: Vec) -> Result { + // This used to return InvalidCharactersDetected, but its more accurate to label + // this as InvalidUtf8Encoding + let validated_utf8_str = + str::from_utf8(&bytes).map_err(|_| ClarityTypeError::InvalidUtf8Encoding)?; let data = validated_utf8_str .chars() .map(|char| { @@ -1108,35 +1259,47 @@ impl Value { )))) } - pub fn expect_ascii(self) -> Result { + /// TODO: remove this comment. For code reviewers. Expect ascii is only called in load_cost_functions and immediately + /// is mapped to a CostError + pub fn expect_ascii(self) -> Result { if let Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data }))) = self { - Ok(String::from_utf8(data) - .map_err(|_| VmInternalError::Expect("Non UTF-8 data in string".into()))?) + String::from_utf8(data).map_err(|_| ClarityTypeError::InvalidUtf8Encoding) } else { error!("Value '{self:?}' is not an ASCII string"); - Err(VmInternalError::Expect("Expected ASCII string".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::STRING_ASCII_MIN), + Box::new(self), + )) } } - pub fn expect_u128(self) -> Result { + pub fn expect_u128(self) -> Result { if let Value::UInt(inner) = self { Ok(inner) } else { error!("Value '{self:?}' is not a u128"); - Err(VmInternalError::Expect("Expected u128".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::UIntType), + Box::new(self), + )) } } - pub fn expect_i128(self) -> Result { + /// TODO: from this comment. For code reviewers. This is only called in tests and immediately unwrwapped. + /// Therefore, its returned value is not currently important. + pub fn expect_i128(self) -> Result { if let Value::Int(inner) = self { Ok(inner) } else { error!("Value '{self:?}' is not an i128"); - Err(VmInternalError::Expect("Expected i128".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::IntType), + Box::new(self), + )) } } - pub fn expect_buff(self, sz: usize) -> Result, VmExecutionError> { + pub fn expect_buff(self, sz: usize) -> Result, ClarityTypeError> { if let Value::Sequence(SequenceData::Buffer(buffdata)) = self { if buffdata.data.len() <= sz { Ok(buffdata.data) @@ -1145,24 +1308,32 @@ impl Value { "Value buffer has len {}, expected {sz}", buffdata.data.len() ); - Err(VmInternalError::Expect("Unexpected buff length".into()).into()) + Err(ClarityTypeError::ValueOutOfBounds) } } else { error!("Value '{self:?}' is not a buff"); - Err(VmInternalError::Expect("Expected buff".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::BUFFER_MIN), + Box::new(self), + )) } } - pub fn expect_list(self) -> Result, VmExecutionError> { + pub fn expect_list(self) -> Result, ClarityTypeError> { if let Value::Sequence(SequenceData::List(listdata)) = self { Ok(listdata.data) } else { error!("Value '{self:?}' is not a list"); - Err(VmInternalError::Expect("Expected list".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::SequenceType(SequenceSubtype::ListType( + TypeSignature::empty_list(), + ))), + Box::new(self), + )) } } - pub fn expect_buff_padded(self, sz: usize, pad: u8) -> Result, VmExecutionError> { + pub fn expect_buff_padded(self, sz: usize, pad: u8) -> Result, ClarityTypeError> { let mut data = self.expect_buff(sz)?; if sz > data.len() { for _ in data.len()..sz { @@ -1172,25 +1343,34 @@ impl Value { Ok(data) } - pub fn expect_bool(self) -> Result { + /// TODO: remove this comment. For code reviwers: this is only ever called in tests and immediately unwrapped + pub fn expect_bool(self) -> Result { if let Value::Bool(b) = self { Ok(b) } else { error!("Value '{self:?}' is not a bool"); - Err(VmInternalError::Expect("Expected bool".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::BoolType), + Box::new(self), + )) } } - pub fn expect_tuple(self) -> Result { + pub fn expect_tuple(self) -> Result { if let Value::Tuple(data) = self { Ok(data) } else { error!("Value '{self:?}' is not a tuple"); - Err(VmInternalError::Expect("Expected tuple".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + // Unfortunately cannot construct an empty Tuple type + // And to add it now would be intrusive. + Box::new(TypeSignature::NoType), + Box::new(self), + )) } } - pub fn expect_optional(self) -> Result, VmExecutionError> { + pub fn expect_optional(self) -> Result, ClarityTypeError> { if let Value::Optional(opt) = self { match opt.data { Some(boxed_value) => Ok(Some(*boxed_value)), @@ -1198,29 +1378,41 @@ impl Value { } } else { error!("Value '{self:?}' is not an optional"); - Err(VmInternalError::Expect("Expected optional".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::OptionalType(Box::new(TypeSignature::NoType))), + Box::new(self), + )) } } - pub fn expect_principal(self) -> Result { + pub fn expect_principal(self) -> Result { if let Value::Principal(p) = self { Ok(p) } else { error!("Value '{self:?}' is not a principal"); - Err(VmInternalError::Expect("Expected principal".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::PrincipalType), + Box::new(self), + )) } } - pub fn expect_callable(self) -> Result { + /// TODO: remove this comment. For reviwers: this is only called in tests and immediately unwrapped + pub fn expect_callable(self) -> Result { if let Value::CallableContract(t) = self { Ok(t) } else { error!("Value '{self:?}' is not a callable contract"); - Err(VmInternalError::Expect("Expected callable".into()).into()) + // Unfortunately cannot construct an empty Callable type + // And to add it now would be intrusive. + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::NoType), + Box::new(self), + )) } } - pub fn expect_result(self) -> Result, VmExecutionError> { + pub fn expect_result(self) -> Result, ClarityTypeError> { if let Value::Response(res_data) = self { if res_data.committed { Ok(Ok(*res_data.data)) @@ -1229,62 +1421,89 @@ impl Value { } } else { error!("Value '{self:?}' is not a response"); - Err(VmInternalError::Expect("Expected response".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::ResponseType(Box::new(( + TypeSignature::NoType, + TypeSignature::NoType, + )))), + Box::new(self), + )) } } - pub fn expect_result_ok(self) -> Result { - if let Value::Response(res_data) = self { + pub fn expect_result_ok(self) -> Result { + if let Value::Response(res_data) = self.clone() { if res_data.committed { Ok(*res_data.data) } else { error!("Value is not a (ok ..)"); - Err(VmInternalError::Expect("Expected ok response".into()).into()) + Err(ClarityTypeError::ResponseTypeMismatch { + expected_ok: true, + data_committed: false, + }) } } else { error!("Value '{self:?}' is not a response"); - Err(VmInternalError::Expect("Expected response".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::ResponseType(Box::new(( + TypeSignature::NoType, + TypeSignature::NoType, + )))), + Box::new(self), + )) } } - pub fn expect_result_err(self) -> Result { - if let Value::Response(res_data) = self { + /// TODO: remove this comment. For reviewers: only ever called in tests and immediately unwrapped + pub fn expect_result_err(self) -> Result { + if let Value::Response(res_data) = self.clone() { if !res_data.committed { Ok(*res_data.data) } else { error!("Value is not a (err ..)"); - Err(VmInternalError::Expect("Expected err response".into()).into()) + Err(ClarityTypeError::ResponseTypeMismatch { + expected_ok: false, + data_committed: true, + }) } } else { error!("Value '{self:?}' is not a response"); - Err(VmInternalError::Expect("Expected response".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::ResponseType(Box::new(( + TypeSignature::NoType, + TypeSignature::NoType, + )))), + Box::new(self), + )) } } - pub fn expect_string_ascii(self) -> Result { + pub fn expect_string_ascii(self) -> Result { if let Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data }))) = self { - Ok(String::from_utf8(data) - .map_err(|_| VmInternalError::Expect("Non UTF-8 data in string".into()))?) + String::from_utf8(data).map_err(|_| ClarityTypeError::InvalidUtf8Encoding) } else { error!("Value '{self:?}' is not an ASCII string"); - Err(VmInternalError::Expect("Expected ASCII string".into()).into()) + Err(ClarityTypeError::TypeMismatchValue( + Box::new(TypeSignature::STRING_ASCII_MIN), + Box::new(self), + )) } } } impl BuffData { - pub fn len(&self) -> Result { + pub fn len(&self) -> Result { self.data .len() .try_into() - .map_err(|_| VmInternalError::Expect("Data length should be valid".into()).into()) + .map_err(|_| ClarityTypeError::ValueTooLarge) } pub fn as_slice(&self) -> &[u8] { self.data.as_slice() } - fn append(&mut self, other_seq: &mut BuffData) { + pub fn append(&mut self, other_seq: &mut BuffData) { self.data.append(&mut other_seq.data); } @@ -1294,29 +1513,29 @@ impl BuffData { } impl ListData { - pub fn len(&self) -> Result { + pub fn len(&self) -> Result { self.data .len() .try_into() - .map_err(|_| VmInternalError::Expect("Data length should be valid".into()).into()) + .map_err(|_| ClarityTypeError::ValueTooLarge) } pub fn is_empty(&self) -> bool { self.data.is_empty() } - fn append( + pub fn append( &mut self, epoch: &StacksEpochId, other_seq: ListData, - ) -> Result<(), VmExecutionError> { + ) -> Result<(), ClarityTypeError> { 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(epoch, entry_type_a, entry_type_b)?; let max_len = self.type_signature.get_max_len() + other_seq.type_signature.get_max_len(); for item in other_seq.data.into_iter() { let (item, _) = Value::sanitize_value(epoch, &entry_type, item) - .ok_or_else(|| CheckErrorKind::ListTypesMustMatch)?; + .ok_or_else(|| ClarityTypeError::ListTypeMismatch)?; self.data.push(item); } @@ -1330,11 +1549,11 @@ impl ASCIIData { self.data.append(&mut other_seq.data); } - pub fn len(&self) -> Result { + pub fn len(&self) -> Result { self.data .len() .try_into() - .map_err(|_| VmInternalError::Expect("Data length should be valid".into()).into()) + .map_err(|_| ClarityTypeError::ValueTooLarge) } } @@ -1343,11 +1562,11 @@ impl UTF8Data { self.data.append(&mut other_seq.data); } - pub fn len(&self) -> Result { + pub fn len(&self) -> Result { self.data .len() .try_into() - .map_err(|_| VmInternalError::Expect("Data length should be valid".into()).into()) + .map_err(|_| ClarityTypeError::ValueTooLarge) } } @@ -1432,7 +1651,7 @@ impl PrincipalData { self.version() < 32 } - pub fn parse(literal: &str) -> Result { + pub fn parse(literal: &str) -> Result { // be permissive about leading single-quote let literal = literal.strip_prefix('\'').unwrap_or(literal); @@ -1445,30 +1664,27 @@ impl PrincipalData { pub fn parse_qualified_contract_principal( literal: &str, - ) -> Result { + ) -> Result { let contract_id = QualifiedContractIdentifier::parse(literal)?; Ok(PrincipalData::Contract(contract_id)) } pub fn parse_standard_principal( literal: &str, - ) -> Result { + ) -> Result { let (version, data) = c32::c32_address_decode(literal).map_err(|x| { - // This `TypeParseFailure` is unreachable in normal Clarity execution. + // This `InvalidPrincipalLiteral` is unreachable in normal Clarity execution. // - All principal literals are validated by the Clarity lexer *before* reaching `parse_standard_principal`. // - The lexer rejects any literal containing characters outside the C32 alphabet. // Therefore, only malformed input fed directly into low-level VM entry points can cause this branch to execute. - RuntimeError::TypeParseFailure(format!("Invalid principal literal: {x}")) + ClarityTypeError::InvalidPrincipalEncoding(x.to_string()) })?; if data.len() != 20 { - return Err(RuntimeError::TypeParseFailure( - "Invalid principal literal: Expected 20 data bytes.".to_string(), - ) - .into()); + return Err(ClarityTypeError::InvalidPrincipalLength(data.len())); } let mut fixed_data = [0; 20]; fixed_data.copy_from_slice(&data[..20]); - Ok(StandardPrincipalData::new(version, fixed_data)?) + StandardPrincipalData::new(version, fixed_data) } } @@ -1501,6 +1717,8 @@ impl fmt::Display for TraitIdentifier { } } +/// TODO: Do we want to make these return errors? I know in theory its infallible, but there is a lot of +/// in theory infallible that return errors instead of straight expects. impl From for StandardPrincipalData { fn from(addr: StacksAddress) -> Self { let (version, bytes) = addr.destruct(); @@ -1518,6 +1736,8 @@ impl From for PrincipalData { } } +/// TODO: Do we want to make these return errors? I know in theory its infallible, but there is a lot of +/// in theory infallible that return errors instead of straight expects. impl From for StacksAddress { fn from(o: StandardPrincipalData) -> StacksAddress { // should be infallible because it's impossible to construct a StandardPrincipalData with @@ -1601,7 +1821,7 @@ impl TupleData { // TODO: add tests from mutation testing results #4833 #[cfg_attr(test, mutants::skip)] - pub fn from_data(data: Vec<(ClarityName, Value)>) -> Result { + pub fn from_data(data: Vec<(ClarityName, Value)>) -> Result { let mut type_map = BTreeMap::new(); let mut data_map = BTreeMap::new(); for (name, value) in data.into_iter() { @@ -1610,7 +1830,7 @@ impl TupleData { match entry { Entry::Vacant(e) => e.insert(type_info), Entry::Occupied(_) => { - return Err(CheckErrorKind::NameAlreadyUsed(name.into()).into()); + return Err(ClarityTypeError::DuplicateTupleField(name.into())); } }; data_map.insert(name, value); @@ -1620,34 +1840,52 @@ impl TupleData { } // TODO: add tests from mutation testing results #4834 + // TODO: remove this comment. This is to help reviewers: from_data_typed is only called in + // serialization.rs where its returned error is immediately ignored. Therefore changes to the error + // types in here are not consensus-breaking #[cfg_attr(test, mutants::skip)] pub fn from_data_typed( epoch: &StacksEpochId, data: Vec<(ClarityName, Value)>, expected: &TupleTypeSignature, - ) -> Result { + ) -> Result { let mut data_map = BTreeMap::new(); + for (name, value) in data.into_iter() { - let expected_type = expected - .field_type(&name) - .ok_or(VmInternalError::FailureConstructingTupleWithType)?; - if !expected_type.admits(epoch, &value)? { - return Err(VmInternalError::FailureConstructingTupleWithType.into()); + // User provided a field not declared in the expected tuple type + let expected_type = expected.field_type(&name).ok_or_else(|| { + ClarityTypeError::NoSuchTupleField(name.to_string(), expected.clone()) + })?; + + // User provided a value that does not match the declared field type + let admits = expected_type.admits(epoch, &value).map_err(|_| { + ClarityTypeError::TypeMismatchValue( + Box::new(expected_type.clone()), + Box::new(value.clone()), + ) + })?; + if !admits { + return Err(ClarityTypeError::TypeMismatchValue( + Box::new(expected_type.clone()), + Box::new(value), + )); } + data_map.insert(name, value); } + Ok(Self::new(expected.clone(), data_map)) } - pub fn get(&self, name: &str) -> Result<&Value, VmExecutionError> { + pub fn get(&self, name: &str) -> Result<&Value, ClarityTypeError> { self.data_map.get(name).ok_or_else(|| { - CheckErrorKind::NoSuchTupleField(name.to_string(), self.type_signature.clone()).into() + ClarityTypeError::NoSuchTupleField(name.to_string(), self.type_signature.clone()) }) } - pub fn get_owned(mut self, name: &str) -> Result { + pub fn get_owned(mut self, name: &str) -> Result { self.data_map.remove(name).ok_or_else(|| { - CheckErrorKind::NoSuchTupleField(name.to_string(), self.type_signature.clone()).into() + ClarityTypeError::NoSuchTupleField(name.to_string(), self.type_signature.clone()) }) } diff --git a/clarity-types/src/types/serialization.rs b/clarity-types/src/types/serialization.rs index 9747aca13d..3252e913d7 100644 --- a/clarity-types/src/types/serialization.rs +++ b/clarity-types/src/types/serialization.rs @@ -23,29 +23,29 @@ use stacks_common::util::hash::{hex_bytes, to_hex}; use stacks_common::util::retry::BoundReader; use super::{ListTypeData, TupleTypeSignature}; -use crate::errors::analysis::StaticCheckErrorKind; -use crate::errors::{CheckErrorKind, IncomparableError, VmInternalError}; +use crate::errors::IncomparableError; use crate::representations::{ClarityName, ContractName, MAX_STRING_LEN}; use crate::types::{ - BOUND_VALUE_SERIALIZATION_BYTES, BufferLength, CallableData, CharType, MAX_TYPE_DEPTH, - MAX_VALUE_SIZE, OptionalData, PrincipalData, QualifiedContractIdentifier, SequenceData, - SequenceSubtype, StandardPrincipalData, StringSubtype, TupleData, TypeSignature, Value, + BOUND_VALUE_SERIALIZATION_BYTES, BufferLength, CallableData, CharType, ClarityTypeError, + MAX_TYPE_DEPTH, MAX_VALUE_SIZE, OptionalData, PrincipalData, QualifiedContractIdentifier, + SequenceData, SequenceSubtype, StandardPrincipalData, StringSubtype, TupleData, TypeSignature, + Value, }; /// Errors that may occur in serialization or deserialization /// If deserialization failed because the described type is a bad type and -/// a CheckErrorKind is thrown, it gets wrapped in BadTypeError. +/// a ClarityTypeError is thrown, it gets wrapped in BadTypeError. /// Any IOErrrors from the supplied buffer will manifest as IOError variants, /// except for EOF -- if the deserialization code experiences an EOF, it is caught /// and rethrown as DeserializationError #[derive(Debug, PartialEq)] pub enum SerializationError { IOError(IncomparableError), - BadTypeError(CheckErrorKind), - DeserializationError(String), + BadTypeError(ClarityTypeError), DeserializeExpected(Box), LeftoverBytesInDeserialization, - SerializationError(String), + SerializationFailure(String), + DeserializationFailure(String), UnexpectedSerialization, } @@ -79,11 +79,11 @@ impl std::fmt::Display for SerializationError { SerializationError::BadTypeError(e) => { write!(f, "Deserialization error, bad type, caused by: {e}") } - SerializationError::DeserializationError(e) => { - write!(f, "Deserialization error: {e}") + SerializationError::DeserializationFailure(e) => { + write!(f, "Deserialization failure: {e}") } - SerializationError::SerializationError(e) => { - write!(f, "Serialization error: {e}") + SerializationError::SerializationFailure(e) => { + write!(f, "Serialization failure: {e}") } SerializationError::DeserializeExpected(e) => write!( f, @@ -119,12 +119,12 @@ impl From for SerializationError { impl From<&str> for SerializationError { fn from(e: &str) -> Self { - SerializationError::DeserializationError(e.into()) + SerializationError::DeserializationFailure(e.into()) } } -impl From for SerializationError { - fn from(e: CheckErrorKind) -> Self { +impl From for SerializationError { + fn from(e: ClarityTypeError) -> Self { SerializationError::BadTypeError(e) } } @@ -232,7 +232,7 @@ macro_rules! serialize_guarded_string { r.read_exact(&mut len)?; let len = u8::from_be_bytes(len); if len > MAX_STRING_LEN { - return Err(SerializationError::DeserializationError( + return Err(SerializationError::DeserializationFailure( "String too long".to_string(), )); } @@ -395,7 +395,7 @@ impl TypeSignature { /// size of a `(buff 1024*1024)` is `1+1024*1024` because of the /// type prefix byte. However, that is 1 byte larger than the maximum /// buffer size in Clarity. - pub fn max_serialized_size(&self) -> Result { + pub fn max_serialized_size(&self) -> Result { let type_prefix_size = 1; let max_output_size = match self { @@ -406,7 +406,7 @@ impl TypeSignature { // `some` or similar with `result` types). So, when // serializing an object with a `NoType`, the other // branch should always be used. - return Err(StaticCheckErrorKind::CouldNotDetermineSerializationType); + return Err(ClarityTypeError::CouldNotDetermineSerializationType); } TypeSignature::IntType => 16, TypeSignature::UIntType => 16, @@ -418,14 +418,14 @@ impl TypeSignature { .get_max_len() .checked_mul(list_type.get_list_item_type().max_serialized_size()?) .and_then(|x| x.checked_add(list_length_encode)) - .ok_or_else(|| StaticCheckErrorKind::ValueTooLarge)? + .ok_or_else(|| ClarityTypeError::ValueTooLarge)? } TypeSignature::SequenceType(SequenceSubtype::BufferType(buff_length)) => { // u32 length as big-endian bytes let buff_length_encode = 4; u32::from(buff_length) .checked_add(buff_length_encode) - .ok_or_else(|| StaticCheckErrorKind::ValueTooLarge)? + .ok_or_else(|| ClarityTypeError::ValueTooLarge)? } TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( length, @@ -435,7 +435,7 @@ impl TypeSignature { // ascii is 1-byte per character u32::from(length) .checked_add(str_length_encode) - .ok_or_else(|| StaticCheckErrorKind::ValueTooLarge)? + .ok_or_else(|| ClarityTypeError::ValueTooLarge)? } TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::UTF8( length, @@ -446,7 +446,7 @@ impl TypeSignature { u32::from(length) .checked_mul(4) .and_then(|x| x.checked_add(str_length_encode)) - .ok_or_else(|| StaticCheckErrorKind::ValueTooLarge)? + .ok_or_else(|| ClarityTypeError::ValueTooLarge)? } TypeSignature::PrincipalType | TypeSignature::CallableType(_) @@ -469,7 +469,7 @@ impl TypeSignature { .checked_add(1) // length of key-name .and_then(|x| x.checked_add(key.len() as u32)) // ClarityName is ascii-only, so 1 byte per length .and_then(|x| x.checked_add(value_size)) - .ok_or_else(|| StaticCheckErrorKind::ValueTooLarge)?; + .ok_or_else(|| ClarityTypeError::ValueTooLarge)?; } total_size } @@ -478,7 +478,7 @@ impl TypeSignature { Ok(size) => size, // if NoType, then this is just serializing a none // value, which is only the type prefix - Err(StaticCheckErrorKind::CouldNotDetermineSerializationType) => 0, + Err(ClarityTypeError::CouldNotDetermineSerializationType) => 0, Err(e) => return Err(e), } } @@ -486,17 +486,17 @@ impl TypeSignature { let (ok_type, err_type) = response_types.as_ref(); let (ok_type_max_size, no_ok_type) = match ok_type.max_serialized_size() { Ok(size) => (size, false), - Err(StaticCheckErrorKind::CouldNotDetermineSerializationType) => (0, true), + Err(ClarityTypeError::CouldNotDetermineSerializationType) => (0, true), Err(e) => return Err(e), }; let err_type_max_size = match err_type.max_serialized_size() { Ok(size) => size, - Err(StaticCheckErrorKind::CouldNotDetermineSerializationType) => { + Err(ClarityTypeError::CouldNotDetermineSerializationType) => { if no_ok_type { // if both the ok type and the error type are NoType, - // throw a StaticCheckErrorKind. This should not be possible, but the check + // throw a ClarityTypeError. This should not be possible, but the check // is done out of caution. - return Err(StaticCheckErrorKind::CouldNotDetermineSerializationType); + return Err(ClarityTypeError::CouldNotDetermineSerializationType); } else { 0 } @@ -506,13 +506,13 @@ impl TypeSignature { cmp::max(ok_type_max_size, err_type_max_size) } TypeSignature::ListUnionType(_) => { - return Err(StaticCheckErrorKind::CouldNotDetermineSerializationType); + return Err(ClarityTypeError::CouldNotDetermineSerializationType); } }; max_output_size .checked_add(type_prefix_size) - .ok_or_else(|| StaticCheckErrorKind::ValueTooLarge) + .ok_or_else(|| ClarityTypeError::ValueTooLarge) } } @@ -584,7 +584,7 @@ impl Value { UNSANITIZED_DEPTH_CHECK }; if stack.len() > depth_check { - return Err(CheckErrorKind::TypeSignatureTooDeep.into()); + return Err(ClarityTypeError::TypeSignatureTooDeep.into()); } #[allow(clippy::expect_used)] @@ -613,8 +613,7 @@ impl Value { TypePrefix::Buffer => { let mut buffer_len = [0; 4]; r.read_exact(&mut buffer_len)?; - let buffer_len = BufferLength::try_from(u32::from_be_bytes(buffer_len)) - .map_err(CheckErrorKind::from)?; + 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::BufferType( @@ -768,7 +767,7 @@ impl Value { let expected_len = u64::from(len); if len > MAX_VALUE_SIZE { - return Err(SerializationError::DeserializationError( + return Err(SerializationError::DeserializationFailure( "Illegal tuple type".to_string(), )); } @@ -845,8 +844,7 @@ impl Value { 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)) - .map_err(CheckErrorKind::from)?; + let buffer_len = BufferLength::try_from(u32::from_be_bytes(buffer_len))?; if let Some(x) = &expected_type { let passed_test = match x { @@ -871,8 +869,7 @@ impl Value { 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)) - .map_err(CheckErrorKind::from)?; + let total_len = BufferLength::try_from(u32::from_be_bytes(total_len))?; let mut data: Vec = vec![0; u32::from(total_len) as usize]; @@ -1034,7 +1031,7 @@ impl Value { } } - Err(SerializationError::DeserializationError( + Err(SerializationError::DeserializationFailure( "Invalid data: stack ran out before finishing parsing".into(), )) } @@ -1069,7 +1066,7 @@ impl Value { Sequence(List(data)) => { let len_bytes = data .len() - .map_err(|e| SerializationError::SerializationError(e.to_string()))? + .map_err(|e| SerializationError::SerializationFailure(e.to_string()))? .to_be_bytes(); w.write_all(&len_bytes)?; for item in data.data.iter() { @@ -1080,7 +1077,7 @@ impl Value { let len_bytes = u32::from( value .len() - .map_err(|e| SerializationError::SerializationError(e.to_string()))?, + .map_err(|e| SerializationError::SerializationFailure(e.to_string()))?, ) .to_be_bytes(); w.write_all(&len_bytes)?; @@ -1097,7 +1094,7 @@ impl Value { let len_bytes = u32::from( value .len() - .map_err(|e| SerializationError::SerializationError(e.to_string()))?, + .map_err(|e| SerializationError::SerializationFailure(e.to_string()))?, ) .to_be_bytes(); w.write_all(&len_bytes)?; @@ -1105,7 +1102,7 @@ impl Value { } Tuple(data) => { let len_bytes = u32::try_from(data.data_map.len()) - .map_err(|e| SerializationError::SerializationError(e.to_string()))? + .map_err(|e| SerializationError::SerializationFailure(e.to_string()))? .to_be_bytes(); w.write_all(&len_bytes)?; for (key, value) in data.data_map.iter() { @@ -1186,7 +1183,7 @@ impl Value { pub fn serialized_size(&self) -> Result { let mut counter = WriteCounter { count: 0 }; self.serialize_write(&mut counter).map_err(|_| { - SerializationError::DeserializationError( + SerializationError::DeserializationFailure( "Error: Failed to count serialization length of Clarity value".into(), ) })?; @@ -1218,15 +1215,14 @@ impl Write for WriteCounter { } impl Value { - pub fn serialize_to_vec(&self) -> Result, VmInternalError> { + pub fn serialize_to_vec(&self) -> Result, SerializationError> { let mut byte_serialization = Vec::new(); - self.serialize_write(&mut byte_serialization) - .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?; + self.serialize_write(&mut byte_serialization)?; Ok(byte_serialization) } /// This does *not* perform any data sanitization - pub fn serialize_to_hex(&self) -> Result { + pub fn serialize_to_hex(&self) -> Result { let byte_serialization = self.serialize_to_vec()?; Ok(to_hex(byte_serialization.as_slice())) } diff --git a/clarity-types/src/types/signatures.rs b/clarity-types/src/types/signatures.rs index 13ce2becc0..0680fd5171 100644 --- a/clarity-types/src/types/signatures.rs +++ b/clarity-types/src/types/signatures.rs @@ -22,11 +22,9 @@ use std::{cmp, fmt}; use serde::{Deserialize, Serialize}; use stacks_common::types::StacksEpochId; -use crate::errors::CheckErrorKind; -use crate::errors::analysis::{CommonCheckErrorKind, StaticCheckErrorKind}; use crate::representations::{CONTRACT_MAX_NAME_LENGTH, ClarityName, ContractName}; use crate::types::{ - CharType, MAX_TO_ASCII_BUFFER_LEN, MAX_TO_ASCII_RESULT_LEN, MAX_TYPE_DEPTH, + CharType, ClarityTypeError, MAX_TO_ASCII_BUFFER_LEN, MAX_TO_ASCII_RESULT_LEN, MAX_TYPE_DEPTH, MAX_UTF8_VALUE_SIZE, MAX_VALUE_SIZE, PrincipalData, QualifiedContractIdentifier, SequenceData, SequencedValue, StandardPrincipalData, TraitIdentifier, Value, WRAPPER_VALUE_SIZE, }; @@ -122,11 +120,11 @@ impl BufferLength { /// /// This function is primarily intended for internal runtime use, /// and serves as the central place for all integer validation logic. - fn try_from_i128(data: i128) -> Result { + fn try_from_i128(data: i128) -> Result { if data > (MAX_VALUE_SIZE as i128) { - Err(CommonCheckErrorKind::ValueTooLarge) + Err(ClarityTypeError::ValueTooLarge) } else if data < 0 { - Err(CommonCheckErrorKind::ValueOutOfBounds) + Err(ClarityTypeError::ValueOutOfBounds) } else { Ok(BufferLength(data as u32)) } @@ -162,22 +160,22 @@ impl From for u32 { } impl TryFrom for BufferLength { - type Error = CommonCheckErrorKind; - fn try_from(data: u32) -> Result { + type Error = ClarityTypeError; + fn try_from(data: u32) -> Result { Self::try_from(data as i128) } } impl TryFrom for BufferLength { - type Error = CommonCheckErrorKind; - fn try_from(data: usize) -> Result { + type Error = ClarityTypeError; + fn try_from(data: usize) -> Result { Self::try_from(data as i128) } } impl TryFrom for BufferLength { - type Error = CommonCheckErrorKind; - fn try_from(data: i128) -> Result { + type Error = ClarityTypeError; + fn try_from(data: i128) -> Result { Self::try_from_i128(data) } } @@ -203,11 +201,11 @@ impl StringUTF8Length { /// /// This function is primarily intended for internal runtime use, /// and serves as the central place for all integer validation logic. - fn try_from_i128(value: i128) -> Result { + fn try_from_i128(value: i128) -> Result { if value > MAX_UTF8_VALUE_SIZE as i128 { - Err(CommonCheckErrorKind::ValueTooLarge) + Err(ClarityTypeError::ValueTooLarge) } else if value < 0 { - Err(CommonCheckErrorKind::ValueOutOfBounds) + Err(ClarityTypeError::ValueOutOfBounds) } else { Ok(StringUTF8Length(value as u32)) } @@ -243,22 +241,22 @@ impl From for u32 { } impl TryFrom for StringUTF8Length { - type Error = CommonCheckErrorKind; - fn try_from(data: u32) -> Result { + type Error = ClarityTypeError; + fn try_from(data: u32) -> Result { Self::try_from(data as i128) } } impl TryFrom for StringUTF8Length { - type Error = CommonCheckErrorKind; - fn try_from(data: usize) -> Result { + type Error = ClarityTypeError; + fn try_from(data: usize) -> Result { Self::try_from(data as i128) } } impl TryFrom for StringUTF8Length { - type Error = CommonCheckErrorKind; - fn try_from(data: i128) -> Result { + type Error = ClarityTypeError; + fn try_from(data: i128) -> Result { Self::try_from_i128(data) } } @@ -359,10 +357,10 @@ impl ListTypeData { pub fn new_list( entry_type: TypeSignature, max_len: u32, - ) -> Result { + ) -> Result { let would_be_depth = 1 + entry_type.depth(); if would_be_depth > MAX_TYPE_DEPTH { - return Err(CommonCheckErrorKind::TypeSignatureTooDeep); + return Err(ClarityTypeError::TypeSignatureTooDeep); } let list_data = ListTypeData { @@ -371,9 +369,9 @@ impl ListTypeData { }; let would_be_size = list_data .inner_size()? - .ok_or_else(|| CommonCheckErrorKind::ValueTooLarge)?; + .ok_or_else(|| ClarityTypeError::ValueTooLarge)?; if would_be_size > MAX_VALUE_SIZE { - Err(CommonCheckErrorKind::ValueTooLarge) + Err(ClarityTypeError::ValueTooLarge) } else { Ok(list_data) } @@ -401,13 +399,13 @@ impl ListTypeData { } impl TypeSignature { - pub fn new_option(inner_type: TypeSignature) -> Result { + pub fn new_option(inner_type: TypeSignature) -> Result { let new_size = WRAPPER_VALUE_SIZE + inner_type.size()?; let new_depth = 1 + inner_type.depth(); if new_size > MAX_VALUE_SIZE { - Err(CommonCheckErrorKind::ValueTooLarge) + Err(ClarityTypeError::ValueTooLarge) } else if new_depth > MAX_TYPE_DEPTH { - Err(CommonCheckErrorKind::TypeSignatureTooDeep) + Err(ClarityTypeError::TypeSignatureTooDeep) } else { Ok(OptionalType(Box::new(inner_type))) } @@ -416,14 +414,14 @@ impl TypeSignature { pub fn new_response( ok_type: TypeSignature, err_type: TypeSignature, - ) -> Result { + ) -> Result { let new_size = WRAPPER_VALUE_SIZE + cmp::max(ok_type.size()?, err_type.size()?); let new_depth = 1 + cmp::max(ok_type.depth(), err_type.depth()); if new_size > MAX_VALUE_SIZE { - Err(CommonCheckErrorKind::ValueTooLarge) + Err(ClarityTypeError::ValueTooLarge) } else if new_depth > MAX_TYPE_DEPTH { - Err(CommonCheckErrorKind::TypeSignatureTooDeep) + Err(ClarityTypeError::TypeSignatureTooDeep) } else { Ok(ResponseType(Box::new((ok_type, err_type)))) } @@ -437,7 +435,7 @@ impl TypeSignature { &TypeSignature::NoType == self } - pub fn admits(&self, epoch: &StacksEpochId, x: &Value) -> Result { + pub fn admits(&self, epoch: &StacksEpochId, x: &Value) -> Result { let x_type = TypeSignature::type_of(x)?; self.admits_type(epoch, &x_type) } @@ -446,7 +444,7 @@ impl TypeSignature { &self, epoch: &StacksEpochId, other: &TypeSignature, - ) -> Result { + ) -> Result { match epoch { StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => self.admits_type_v2_0(other), StacksEpochId::Epoch21 @@ -458,13 +456,11 @@ impl TypeSignature { | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 | StacksEpochId::Epoch33 => self.admits_type_v2_1(other), - StacksEpochId::Epoch10 => Err(CommonCheckErrorKind::Expects( - "epoch 1.0 not supported".into(), - )), + StacksEpochId::Epoch10 => Err(ClarityTypeError::UnsupportedEpoch(*epoch)), } } - pub fn admits_type_v2_0(&self, other: &TypeSignature) -> Result { + pub fn admits_type_v2_0(&self, other: &TypeSignature) -> Result { match self { SequenceType(SequenceSubtype::ListType(my_list_type)) => { if let SequenceType(SequenceSubtype::ListType(other_list_type)) = other { @@ -546,18 +542,16 @@ impl TypeSignature { Ok(false) } } - NoType => Err(CommonCheckErrorKind::CouldNotDetermineType), - CallableType(_) => Err(CommonCheckErrorKind::Expects( - "CallableType should not be used in epoch v2.0".into(), - )), - ListUnionType(_) => Err(CommonCheckErrorKind::Expects( - "ListUnionType should not be used in epoch v2.0".into(), + NoType => Err(ClarityTypeError::CouldNotDetermineType), + CallableType(_) | ListUnionType(_) => Err(ClarityTypeError::UnsupportedTypeInEpoch( + Box::new(self.clone()), + StacksEpochId::Epoch20, )), _ => Ok(other == self), } } - fn admits_type_v2_1(&self, other: &TypeSignature) -> Result { + fn admits_type_v2_1(&self, other: &TypeSignature) -> Result { let other = match other.concretize() { Ok(other) => other, Err(_) => { @@ -646,7 +640,7 @@ impl TypeSignature { Ok(false) } } - NoType => Err(CommonCheckErrorKind::CouldNotDetermineType), + NoType => Err(ClarityTypeError::CouldNotDetermineType), _ => Ok(&other == self), } } @@ -703,7 +697,7 @@ impl TypeSignature { /// Concretize the type. The input to this method may include /// `ListUnionType` and the `CallableType` variant for a `principal. /// This method turns these "temporary" types into actual types. - pub fn concretize(&self) -> Result { + pub fn concretize(&self) -> Result { match self { ListUnionType(types) => { let mut is_trait = None; @@ -712,7 +706,7 @@ impl TypeSignature { match partial { CallableSubtype::Principal(_) => { if is_trait.is_some() { - return Err(StaticCheckErrorKind::TypeError( + return Err(ClarityTypeError::TypeMismatch( Box::new(TypeSignature::CallableType(partial.clone())), Box::new(TypeSignature::PrincipalType), )); @@ -722,7 +716,7 @@ impl TypeSignature { } CallableSubtype::Trait(t) => { if is_principal { - return Err(StaticCheckErrorKind::TypeError( + return Err(ClarityTypeError::TypeMismatch( Box::new(TypeSignature::PrincipalType), Box::new(TypeSignature::CallableType(partial.clone())), )); @@ -745,12 +739,12 @@ impl TypeSignature { } impl TryFrom> for TupleTypeSignature { - type Error = CommonCheckErrorKind; + type Error = ClarityTypeError; fn try_from( type_data: Vec<(ClarityName, TypeSignature)>, - ) -> Result { + ) -> Result { if type_data.is_empty() { - return Err(CommonCheckErrorKind::EmptyTuplesNotAllowed); + return Err(ClarityTypeError::EmptyTuplesNotAllowed); } let mut type_map = BTreeMap::new(); @@ -758,7 +752,7 @@ impl TryFrom> for TupleTypeSignature { if let Entry::Vacant(e) = type_map.entry(name.clone()) { e.insert(type_info); } else { - return Err(CommonCheckErrorKind::NameAlreadyUsed(name.into())); + return Err(ClarityTypeError::DuplicateTupleField(name.into())); } } TupleTypeSignature::try_from(type_map) @@ -766,25 +760,25 @@ impl TryFrom> for TupleTypeSignature { } impl TryFrom> for TupleTypeSignature { - type Error = CommonCheckErrorKind; + type Error = ClarityTypeError; fn try_from( type_map: BTreeMap, - ) -> Result { + ) -> Result { if type_map.is_empty() { - return Err(CommonCheckErrorKind::EmptyTuplesNotAllowed); + return Err(ClarityTypeError::EmptyTuplesNotAllowed); } for child_sig in type_map.values() { if (1 + child_sig.depth()) > MAX_TYPE_DEPTH { - return Err(CommonCheckErrorKind::TypeSignatureTooDeep); + return Err(ClarityTypeError::TypeSignatureTooDeep); } } let type_map = Arc::new(type_map.into_iter().collect()); let result = TupleTypeSignature { type_map }; let would_be_size = result .inner_size()? - .ok_or_else(|| CommonCheckErrorKind::ValueTooLarge)?; + .ok_or_else(|| ClarityTypeError::ValueTooLarge)?; if would_be_size > MAX_VALUE_SIZE { - Err(CommonCheckErrorKind::ValueTooLarge) + Err(ClarityTypeError::ValueTooLarge) } else { Ok(result) } @@ -814,7 +808,7 @@ impl TupleTypeSignature { &self, epoch: &StacksEpochId, other: &TupleTypeSignature, - ) -> Result { + ) -> Result { if self.type_map.len() != other.type_map.len() { return Ok(false); } @@ -935,18 +929,18 @@ impl TypeSignature { /// Creates a string ASCII type with the specified length. /// Returns an error if the provided length is invalid. - pub fn new_ascii_type(len: i128) -> Result { + pub fn new_ascii_type(len: i128) -> Result { Ok(SequenceType(SequenceSubtype::StringType( StringSubtype::ASCII(BufferLength::try_from_i128(len)?), ))) } /// If one of the types is a NoType, return Ok(the other type), otherwise return least_supertype(a, b) - pub(crate) fn factor_out_no_type( + pub fn factor_out_no_type( epoch: &StacksEpochId, a: &TypeSignature, b: &TypeSignature, - ) -> Result { + ) -> Result { if a.is_no_type() { Ok(b.clone()) } else if b.is_no_type() { @@ -997,7 +991,7 @@ impl TypeSignature { epoch: &StacksEpochId, a: &TypeSignature, b: &TypeSignature, - ) -> Result { + ) -> Result { match epoch { StacksEpochId::Epoch20 | StacksEpochId::Epoch2_05 => Self::least_supertype_v2_0(a, b), StacksEpochId::Epoch21 @@ -1009,16 +1003,14 @@ impl TypeSignature { | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 | StacksEpochId::Epoch33 => Self::least_supertype_v2_1(a, b), - StacksEpochId::Epoch10 => Err(CommonCheckErrorKind::Expects( - "epoch 1.0 not supported".into(), - )), + StacksEpochId::Epoch10 => Err(ClarityTypeError::UnsupportedEpoch(*epoch)), } } - fn least_supertype_v2_0( + pub fn least_supertype_v2_0( a: &TypeSignature, b: &TypeSignature, - ) -> Result { + ) -> Result { match (a, b) { ( TupleType(TupleTypeSignature { type_map: types_a }), @@ -1026,7 +1018,7 @@ impl TypeSignature { ) => { let mut type_map_out = BTreeMap::new(); for (name, entry_a) in types_a.iter() { - let entry_b = types_b.get(name).ok_or(CommonCheckErrorKind::TypeError( + let entry_b = types_b.get(name).ok_or(ClarityTypeError::TypeMismatch( Box::new(a.clone()), Box::new(b.clone()), ))?; @@ -1035,7 +1027,7 @@ impl TypeSignature { } Ok(TupleTypeSignature::try_from(type_map_out) .map(|x| x.into()) - .map_err(|_| CommonCheckErrorKind::SupertypeTooLarge)?) + .map_err(|_| ClarityTypeError::SupertypeTooLarge)?) } ( SequenceType(SequenceSubtype::ListType(ListTypeData { @@ -1056,7 +1048,7 @@ impl TypeSignature { }; let max_len = cmp::max(len_a, len_b); Ok(Self::list_of(entry_type, *max_len) - .map_err(|_| CommonCheckErrorKind::SupertypeTooLarge)?) + .map_err(|_| ClarityTypeError::SupertypeTooLarge)?) } (ResponseType(resp_a), ResponseType(resp_b)) => { let ok_type = @@ -1115,7 +1107,7 @@ impl TypeSignature { if x == y { Ok(x.clone()) } else { - Err(CommonCheckErrorKind::TypeError( + Err(ClarityTypeError::TypeMismatch( Box::new(a.clone()), Box::new(b.clone()), )) @@ -1124,10 +1116,10 @@ impl TypeSignature { } } - pub(crate) fn least_supertype_v2_1( + pub fn least_supertype_v2_1( a: &TypeSignature, b: &TypeSignature, - ) -> Result { + ) -> Result { match (a, b) { ( TupleType(TupleTypeSignature { type_map: types_a }), @@ -1135,7 +1127,7 @@ impl TypeSignature { ) => { let mut type_map_out = BTreeMap::new(); for (name, entry_a) in types_a.iter() { - let entry_b = types_b.get(name).ok_or(CommonCheckErrorKind::TypeError( + let entry_b = types_b.get(name).ok_or(ClarityTypeError::TypeMismatch( Box::new(a.clone()), Box::new(b.clone()), ))?; @@ -1144,7 +1136,7 @@ impl TypeSignature { } Ok(TupleTypeSignature::try_from(type_map_out) .map(|x| x.into()) - .map_err(|_| CommonCheckErrorKind::SupertypeTooLarge)?) + .map_err(|_| ClarityTypeError::SupertypeTooLarge)?) } ( SequenceType(SequenceSubtype::ListType(ListTypeData { @@ -1165,7 +1157,7 @@ impl TypeSignature { }; let max_len = cmp::max(len_a, len_b); Ok(Self::list_of(entry_type, *max_len) - .map_err(|_| CommonCheckErrorKind::SupertypeTooLarge)?) + .map_err(|_| ClarityTypeError::SupertypeTooLarge)?) } (ResponseType(resp_a), ResponseType(resp_b)) => { let ok_type = @@ -1246,7 +1238,7 @@ impl TypeSignature { if all_principals { Ok(PrincipalType) } else { - Err(CommonCheckErrorKind::TypeError( + Err(ClarityTypeError::TypeMismatch( Box::new(a.clone()), Box::new(b.clone()), )) @@ -1259,7 +1251,7 @@ impl TypeSignature { if x == y { Ok(x.clone()) } else { - Err(CommonCheckErrorKind::TypeError( + Err(ClarityTypeError::TypeMismatch( Box::new(a.clone()), Box::new(b.clone()), )) @@ -1271,7 +1263,7 @@ impl TypeSignature { pub fn list_of( item_type: TypeSignature, max_len: u32, - ) -> Result { + ) -> Result { ListTypeData::new_list(item_type, max_len).map(|x| x.into()) } @@ -1282,7 +1274,7 @@ impl TypeSignature { } } - pub fn type_of(x: &Value) -> Result { + pub fn type_of(x: &Value) -> Result { let out = match x { Value::Principal(_) => PrincipalType, Value::Int(_v) => IntType, @@ -1311,31 +1303,29 @@ impl TypeSignature { Ok(out) } - pub fn literal_type_of(x: &Value) -> Result { + pub fn literal_type_of(x: &Value) -> Result { match x { Value::Principal(PrincipalData::Contract(contract_id)) => Ok(CallableType( CallableSubtype::Principal(contract_id.clone()), )), - _ => Self::type_of(x).map_err(StaticCheckErrorKind::from), + _ => Self::type_of(x), } } // Checks if resulting type signature is of valid size. - pub fn construct_parent_list_type(args: &[Value]) -> Result { - let children_types: Result, _> = args.iter().map(TypeSignature::type_of).collect(); - Ok(TypeSignature::parent_list_type(&children_types?)?) + pub fn construct_parent_list_type(args: &[Value]) -> Result { + let children_types: Result, ClarityTypeError> = + args.iter().map(TypeSignature::type_of).collect(); + TypeSignature::parent_list_type(&children_types?) } - pub fn parent_list_type( - children: &[TypeSignature], - ) -> Result { + pub fn parent_list_type(children: &[TypeSignature]) -> Result { if let Some((first, rest)) = children.split_first() { let mut current_entry_type = first.clone(); for next_entry in rest.iter() { current_entry_type = Self::least_supertype_v2_1(¤t_entry_type, next_entry)?; } - let len = - u32::try_from(children.len()).map_err(|_| CommonCheckErrorKind::ValueTooLarge)?; + let len = u32::try_from(children.len()).map_err(|_| ClarityTypeError::ValueTooLarge)?; ListTypeData::new_list(current_entry_type, len) } else { Ok(TypeSignature::empty_list()) @@ -1375,16 +1365,12 @@ impl TypeSignature { } } - pub fn size(&self) -> Result { - self.inner_size()?.ok_or_else(|| { - CommonCheckErrorKind::Expects( - "FAIL: .size() overflowed on too large of a type. construction should have failed!" - .into(), - ) - }) + pub fn size(&self) -> Result { + self.inner_size()? + .ok_or_else(|| ClarityTypeError::ValueTooLarge) } - fn inner_size(&self) -> Result, CommonCheckErrorKind> { + fn inner_size(&self) -> Result, ClarityTypeError> { let out = 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). @@ -1417,9 +1403,9 @@ impl TypeSignature { Ok(out) } - pub fn type_size(&self) -> Result { + pub fn type_size(&self) -> Result { self.inner_type_size() - .ok_or_else(|| CommonCheckErrorKind::ValueTooLarge) + .ok_or_else(|| ClarityTypeError::ValueTooLarge) } /// Returns the size of the _type signature_ @@ -1449,7 +1435,7 @@ impl TypeSignature { impl ListTypeData { /// List Size: type_signature_size + max_len * entry_type.size() - fn inner_size(&self) -> Result, CommonCheckErrorKind> { + fn inner_size(&self) -> Result, ClarityTypeError> { let total_size = self .entry_type .size()? @@ -1498,10 +1484,9 @@ impl TupleTypeSignature { } } - pub fn size(&self) -> Result { - self.inner_size()?.ok_or_else(|| { - CheckErrorKind::Expects("size() overflowed on a constructed type.".into()) - }) + pub fn size(&self) -> Result { + self.inner_size()? + .ok_or_else(|| ClarityTypeError::ValueTooLarge) } fn max_depth(&self) -> u8 { @@ -1515,7 +1500,7 @@ impl TupleTypeSignature { /// Tuple Size: /// size( btreemap ) + type_size /// size( btreemap ) = 2*map.len() + sum(names) + sum(values) - fn inner_size(&self) -> Result, CommonCheckErrorKind> { + fn inner_size(&self) -> Result, ClarityTypeError> { let Some(mut total_size) = u32::try_from(self.type_map.len()) .ok() .and_then(|x| x.checked_mul(2)) diff --git a/clarity/fuzz/fuzz_targets/fuzz_sanitize.rs b/clarity/fuzz/fuzz_targets/fuzz_sanitize.rs index 298151e096..aa5b116eb5 100644 --- a/clarity/fuzz/fuzz_targets/fuzz_sanitize.rs +++ b/clarity/fuzz/fuzz_targets/fuzz_sanitize.rs @@ -16,6 +16,7 @@ #![no_main] use arbitrary::Arbitrary; +use clarity::vm::errors::ClarityTypeError; use clarity::vm::analysis::CheckErrorKind; use clarity::vm::representations::ContractName; use clarity::vm::types::serialization::SerializationError; @@ -277,7 +278,7 @@ fn fuzz_sanitize(input: ClarityValue) { deserialize_unsanitized.unwrap_err(); } else { let deser_value = match deserialize_unsanitized { - Err(SerializationError::BadTypeError(CheckErrorKind::TypeSignatureTooDeep)) => { + Err(SerializationError::BadTypeError(ClarityTypeError::TypeSignatureTooDeep)) => { // pre-2.4, deserializer could error on types deeper than a deserialization limit of 16. // with sanitization enabled (a 2.4-gated feature), these serializations are readable. ClarityValue::deserialize_read( @@ -298,7 +299,7 @@ fn fuzz_sanitize(input: ClarityValue) { true, ) { Ok(x) => x, - Err(SerializationError::BadTypeError(CheckErrorKind::TypeSignatureTooDeep)) => { + Err(SerializationError::BadTypeError(ClarityTypeError::TypeSignatureTooDeep)) => { assert!(!did_strict_admit, "Unsanitized inputs may fail to deserialize, but they must have needed sanitization"); // check that the sanitized value *is* readable let serialized = sanitized_value @@ -309,7 +310,7 @@ fn fuzz_sanitize(input: ClarityValue) { Some(&computed_type), false, ) { - Err(SerializationError::BadTypeError(CheckErrorKind::TypeSignatureTooDeep)) => { + Err(SerializationError::BadTypeError(ClarityTypeError::TypeSignatureTooDeep)) => { // pre-2.4, deserializer could error on legal types deeper than a deserialization limit of 16. // with sanitization enabled (a 2.4-gated feature), these serializations are readable. ClarityValue::deserialize_read( diff --git a/clarity/src/vm/analysis/analysis_db.rs b/clarity/src/vm/analysis/analysis_db.rs index ff7197249f..623fee4cb6 100644 --- a/clarity/src/vm/analysis/analysis_db.rs +++ b/clarity/src/vm/analysis/analysis_db.rs @@ -51,11 +51,11 @@ impl<'a> AnalysisDatabase<'a> { self.begin(); let result = f(self).or_else(|e| { self.roll_back() - .map_err(|e| StaticCheckErrorKind::Expects(format!("{e:?}")))?; + .map_err(|e| StaticCheckErrorKind::ExpectsRejectable(format!("{e:?}")))?; Err(e) })?; self.commit() - .map_err(|e| StaticCheckErrorKind::Expects(format!("{e:?}")))?; + .map_err(|e| StaticCheckErrorKind::ExpectsRejectable(format!("{e:?}")))?; Ok(result) } @@ -66,13 +66,13 @@ impl<'a> AnalysisDatabase<'a> { pub fn commit(&mut self) -> Result<(), StaticCheckError> { self.store .commit() - .map_err(|e| StaticCheckErrorKind::Expects(format!("{e:?}")).into()) + .map_err(|e| StaticCheckErrorKind::ExpectsRejectable(format!("{e:?}")).into()) } pub fn roll_back(&mut self) -> Result<(), StaticCheckError> { self.store .rollback() - .map_err(|e| StaticCheckErrorKind::Expects(format!("{e:?}")).into()) + .map_err(|e| StaticCheckErrorKind::ExpectsRejectable(format!("{e:?}")).into()) } pub fn storage_key() -> &'static str { @@ -108,7 +108,8 @@ impl<'a> AnalysisDatabase<'a> { .flatten() .map(|x| { ContractAnalysis::deserialize(&x).map_err(|_| { - StaticCheckErrorKind::Expects("Bad data deserialized from DB".into()).into() + StaticCheckErrorKind::ExpectsRejectable("Bad data deserialized from DB".into()) + .into() }) }) .transpose() @@ -128,7 +129,7 @@ impl<'a> AnalysisDatabase<'a> { .flatten() .map(|x| { ContractAnalysis::deserialize(&x).map_err(|_| { - StaticCheckErrorKind::Expects("Bad data deserialized from DB".into()) + StaticCheckErrorKind::ExpectsRejectable("Bad data deserialized from DB".into()) }) }) .transpose()? @@ -153,7 +154,7 @@ impl<'a> AnalysisDatabase<'a> { self.store .insert_metadata(contract_identifier, key, &contract.serialize()) - .map_err(|e| StaticCheckErrorKind::Expects(format!("{e:?}")))?; + .map_err(|e| StaticCheckErrorKind::ExpectsRejectable(format!("{e:?}")))?; Ok(()) } diff --git a/clarity/src/vm/analysis/contract_interface_builder/mod.rs b/clarity/src/vm/analysis/contract_interface_builder/mod.rs index 54f1a161cf..ed64dc7ef5 100644 --- a/clarity/src/vm/analysis/contract_interface_builder/mod.rs +++ b/clarity/src/vm/analysis/contract_interface_builder/mod.rs @@ -278,7 +278,7 @@ impl ContractInterfaceFunction { FunctionType::Fixed(FixedFunction { returns, .. }) => { ContractInterfaceAtomType::from_type_signature(returns) } - _ => return Err(StaticCheckErrorKind::Expects( + _ => return Err(StaticCheckErrorKind::ExpectsRejectable( "Contract functions should only have fixed function return types!" .into(), ) @@ -290,7 +290,7 @@ impl ContractInterfaceFunction { ContractInterfaceFunctionArg::from_function_args(args) } _ => { - return Err(StaticCheckErrorKind::Expects( + return Err(StaticCheckErrorKind::ExpectsRejectable( "Contract functions should only have fixed function arguments!" .into(), ) @@ -402,7 +402,8 @@ impl ContractInterface { pub fn serialize(&self) -> Result { serde_json::to_string(self).map_err(|_| { - StaticCheckErrorKind::Expects("Failed to serialize contract interface".into()).into() + StaticCheckErrorKind::ExpectsRejectable("Failed to serialize contract interface".into()) + .into() }) } } diff --git a/clarity/src/vm/analysis/mod.rs b/clarity/src/vm/analysis/mod.rs index fac0cce6e7..c3397d654c 100644 --- a/clarity/src/vm/analysis/mod.rs +++ b/clarity/src/vm/analysis/mod.rs @@ -57,7 +57,7 @@ pub fn mem_type_check( ) -> Result<(Option, ContractAnalysis), StaticCheckError> { let contract_identifier = QualifiedContractIdentifier::transient(); let contract = build_ast(&contract_identifier, snippet, &mut (), version, epoch) - .map_err(|e| StaticCheckErrorKind::Expects(format!("Failed to build AST: {e}")))? + .map_err(|e| StaticCheckErrorKind::ExpectsRejectable(format!("Failed to build AST: {e}")))? .expressions; let mut marf = MemoryBackingStore::new(); @@ -76,14 +76,16 @@ pub fn mem_type_check( Ok(x) => { // return the first type result of the type checker - let first_type = - x.type_map - .as_ref() - .ok_or_else(|| StaticCheckErrorKind::Expects("Should be non-empty".into()))? - .get_type_expected(x.expressions.last().ok_or_else(|| { - StaticCheckErrorKind::Expects("Should be non-empty".into()) - })?) - .cloned(); + let first_type = x + .type_map + .as_ref() + .ok_or_else(|| { + StaticCheckErrorKind::ExpectsRejectable("Should be non-empty".into()) + })? + .get_type_expected(x.expressions.last().ok_or_else(|| { + StaticCheckErrorKind::ExpectsRejectable("Should be non-empty".into()) + })?) + .cloned(); Ok((first_type, x)) } Err(e) => Err(e.0), @@ -152,7 +154,7 @@ pub fn run_analysis( TypeChecker2_1::run_pass(&epoch, &mut contract_analysis, db, build_type_map) } StacksEpochId::Epoch10 => { - return Err(StaticCheckErrorKind::Expects( + return Err(StaticCheckErrorKind::ExpectsRejectable( "Epoch 1.0 is not a valid epoch for analysis".into(), ) .into()) diff --git a/clarity/src/vm/analysis/type_checker/mod.rs b/clarity/src/vm/analysis/type_checker/mod.rs index 30db2e931f..eb294a2fcc 100644 --- a/clarity/src/vm/analysis/type_checker/mod.rs +++ b/clarity/src/vm/analysis/type_checker/mod.rs @@ -48,9 +48,10 @@ impl FunctionType { | StacksEpochId::Epoch31 | StacksEpochId::Epoch32 | StacksEpochId::Epoch33 => self.check_args_2_1(accounting, args, clarity_version), - StacksEpochId::Epoch10 => { - Err(StaticCheckErrorKind::Expects("Epoch10 is not supported".into()).into()) - } + StacksEpochId::Epoch10 => Err(StaticCheckErrorKind::ExpectsRejectable( + "Epoch10 is not supported".into(), + ) + .into()), } } @@ -76,9 +77,10 @@ impl FunctionType { | StacksEpochId::Epoch33 => { self.check_args_by_allowing_trait_cast_2_1(db, clarity_version, func_args) } - StacksEpochId::Epoch10 => { - Err(StaticCheckErrorKind::Expects("Epoch10 is not supported".into()).into()) - } + StacksEpochId::Epoch10 => Err(StaticCheckErrorKind::ExpectsRejectable( + "Epoch10 is not supported".into(), + ) + .into()), } } } diff --git a/clarity/src/vm/analysis/type_checker/v2_05/mod.rs b/clarity/src/vm/analysis/type_checker/v2_05/mod.rs index 8ccd89410b..252d524868 100644 --- a/clarity/src/vm/analysis/type_checker/v2_05/mod.rs +++ b/clarity/src/vm/analysis/type_checker/v2_05/mod.rs @@ -249,7 +249,7 @@ impl FunctionType { Ok(TypeSignature::BoolType) } - FunctionType::Binary(_, _, _) => Err(StaticCheckErrorKind::Expects( + FunctionType::Binary(_, _, _) => Err(StaticCheckErrorKind::ExpectsRejectable( "Binary type should not be reached in 2.05".into(), ) .into()), @@ -264,7 +264,10 @@ impl FunctionType { let (expected_args, returns) = match self { FunctionType::Fixed(FixedFunction { args, returns }) => (args, returns), _ => { - return Err(StaticCheckErrorKind::Expects("Unexpected function type".into()).into()) + return Err(StaticCheckErrorKind::ExpectsRejectable( + "Unexpected function type".into(), + ) + .into()) } }; check_argument_count(expected_args.len(), func_args)?; @@ -337,13 +340,13 @@ fn type_reserved_variable(variable_name: &str) -> Result, BlockHeight => TypeSignature::UIntType, BurnBlockHeight => TypeSignature::UIntType, NativeNone => TypeSignature::new_option(no_type()) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, NativeTrue => TypeSignature::BoolType, NativeFalse => TypeSignature::BoolType, TotalLiquidMicroSTX => TypeSignature::UIntType, Regtest => TypeSignature::BoolType, TxSponsor | Mainnet | ChainId | StacksBlockHeight | TenureHeight | StacksBlockTime | CurrentContract => { - return Err(StaticCheckErrorKind::Expects( + return Err(StaticCheckErrorKind::ExpectsRejectable( "tx-sponsor, mainnet, chain-id, stacks-block-height, tenure-height, stacks-block-time, and current-contract should not reach here in 2.05".into(), ) .into()) @@ -434,7 +437,9 @@ impl<'a, 'b> TypeChecker<'a, 'b> { } Err(e) => Err(e), })? - .ok_or_else(|| StaticCheckErrorKind::Expects("Expected a depth result".into()))?; + .ok_or_else(|| { + StaticCheckErrorKind::ExpectsRejectable("Expected a depth result".into()) + })?; } runtime_cost(ClarityCostFunction::AnalysisStorage, self, size)?; @@ -605,7 +610,7 @@ impl<'a, 'b> TypeChecker<'a, 'b> { )?; if self.function_return_tracker.is_some() { - return Err(StaticCheckErrorKind::Expects( + return Err(StaticCheckErrorKind::ExpectsRejectable( "Interpreter error: Previous function define left dirty typecheck state.".into(), ) .into()); diff --git a/clarity/src/vm/analysis/type_checker/v2_05/natives/mod.rs b/clarity/src/vm/analysis/type_checker/v2_05/natives/mod.rs index 1cc33db455..8eb62e0922 100644 --- a/clarity/src/vm/analysis/type_checker/v2_05/natives/mod.rs +++ b/clarity/src/vm/analysis/type_checker/v2_05/natives/mod.rs @@ -510,7 +510,7 @@ fn check_principal_of( checker.type_check_expects(&args[0], context, &TypeSignature::BUFFER_33)?; Ok( TypeSignature::new_response(TypeSignature::PrincipalType, TypeSignature::UIntType) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, ) } @@ -524,7 +524,7 @@ fn check_secp256k1_recover( checker.type_check_expects(&args[1], context, &TypeSignature::BUFFER_65)?; Ok( TypeSignature::new_response(TypeSignature::BUFFER_33, TypeSignature::UIntType) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, ) } @@ -604,7 +604,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::IntType, ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -615,7 +615,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::UIntType, ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -626,7 +626,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::BoolType, ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -679,7 +679,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("owner".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -691,7 +691,7 @@ impl TypedNativeFunction { FunctionArg::new( TypeSignature::UIntType, ClarityName::try_from("amount".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -699,7 +699,7 @@ impl TypedNativeFunction { FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("sender".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -707,7 +707,7 @@ impl TypedNativeFunction { FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("recipient".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -717,14 +717,14 @@ impl TypedNativeFunction { TypeSignature::BoolType, TypeSignature::UIntType, ) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, }))), StxBurn => Simple(SimpleNativeFunction(FunctionType::Fixed(FixedFunction { args: vec![ FunctionArg::new( TypeSignature::UIntType, ClarityName::try_from("amount".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -732,7 +732,7 @@ impl TypedNativeFunction { FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("sender".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -742,7 +742,7 @@ impl TypedNativeFunction { TypeSignature::BoolType, TypeSignature::UIntType, ) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, }))), GetTokenBalance => Special(SpecialNativeFunction(&assets::check_special_get_balance)), GetAssetOwner => Special(SpecialNativeFunction(&assets::check_special_get_owner)), @@ -840,7 +840,7 @@ impl TypedNativeFunction { | AllowanceWithStacking | AllowanceAll | Secp256r1Verify => { - return Err(StaticCheckErrorKind::Expects( + return Err(StaticCheckErrorKind::ExpectsRejectable( "Clarity 2+ keywords should not show up in 2.05".into(), )); } diff --git a/clarity/src/vm/analysis/type_checker/v2_05/natives/sequences.rs b/clarity/src/vm/analysis/type_checker/v2_05/natives/sequences.rs index 37f3d83b83..09feeb0ff9 100644 --- a/clarity/src/vm/analysis/type_checker/v2_05/natives/sequences.rs +++ b/clarity/src/vm/analysis/type_checker/v2_05/natives/sequences.rs @@ -397,21 +397,23 @@ pub fn check_special_element_at( match collection_type { TypeSignature::SequenceType(ListType(list)) => { let (entry_type, _) = list.destruct(); - TypeSignature::new_option(entry_type).map_err(|e| e.into()) + Ok(TypeSignature::new_option(entry_type)?) } TypeSignature::SequenceType(BufferType(_)) => Ok(TypeSignature::OptionalType(Box::new( TypeSignature::BUFFER_1, ))), TypeSignature::SequenceType(StringType(ASCII(_))) => Ok(TypeSignature::OptionalType( Box::new(TypeSignature::SequenceType(StringType(ASCII( - BufferLength::try_from(1u32) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + BufferLength::try_from(1u32).map_err(|_| { + StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()) + })?, )))), )), TypeSignature::SequenceType(StringType(UTF8(_))) => Ok(TypeSignature::OptionalType( Box::new(TypeSignature::SequenceType(StringType(UTF8( - StringUTF8Length::try_from(1u32) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + StringUTF8Length::try_from(1u32).map_err(|_| { + StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()) + })?, )))), )), _ => Err(StaticCheckErrorKind::ExpectedSequence(Box::new(collection_type)).into()), @@ -435,5 +437,5 @@ pub fn check_special_index_of( checker.type_check_expects(&args[1], context, &expected_input_type)?; - TypeSignature::new_option(TypeSignature::UIntType).map_err(|e| e.into()) + Ok(TypeSignature::new_option(TypeSignature::UIntType)?) } diff --git a/clarity/src/vm/analysis/type_checker/v2_1/mod.rs b/clarity/src/vm/analysis/type_checker/v2_1/mod.rs index fbbbc8fb57..f090e2d175 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/mod.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/mod.rs @@ -186,7 +186,7 @@ impl FunctionType { let cost = Some(compute_typecheck_cost(accounting, expected_type, arg_type)); let admitted = match expected_type.admits_type(&StacksEpochId::Epoch21, arg_type) { Ok(admitted) => admitted, - Err(e) => return (cost, Err(e.into())), + Err(e) => return (cost, Err(StaticCheckError::from(e))), }; if !admitted { return ( @@ -219,7 +219,7 @@ impl FunctionType { (cost, return_type) } else { let return_type = accumulated_type - .ok_or_else(|| StaticCheckErrorKind::Expects("Failed to set accumulated type for arg indices >= 1 in variadic arithmetic".into()).into()); + .ok_or_else(|| StaticCheckErrorKind::ExpectsRejectable("Failed to set accumulated type for arg indices >= 1 in variadic arithmetic".into()).into()); let check_result = return_type.and_then(|return_type| { if arg_type != return_type { Err(StaticCheckErrorKind::TypeError( @@ -568,7 +568,10 @@ impl FunctionType { let (expected_args, returns) = match self { FunctionType::Fixed(FixedFunction { args, returns }) => (args, returns), _ => { - return Err(StaticCheckErrorKind::Expects("Unexpected function type".into()).into()) + return Err(StaticCheckErrorKind::ExpectsRejectable( + "Unexpected function type".into(), + ) + .into()) } }; check_argument_count(expected_args.len(), func_args)?; @@ -592,7 +595,9 @@ impl FunctionType { &StacksEpochId::Epoch21, ) .map_err(|_| { - StaticCheckErrorKind::Expects("Failed to get trait".into()) + StaticCheckErrorKind::ExpectsRejectable( + "Failed to get trait".into(), + ) })? .ok_or(StaticCheckErrorKind::NoSuchContract( trait_id.contract_identifier.to_string(), @@ -1021,14 +1026,14 @@ fn type_reserved_variable( let var_type = match variable { TxSender => TypeSignature::PrincipalType, TxSponsor => TypeSignature::new_option(TypeSignature::PrincipalType) - .map_err(|_| StaticCheckErrorKind::Expects("Bad construction".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad construction".into()))?, ContractCaller => TypeSignature::PrincipalType, BlockHeight => TypeSignature::UIntType, StacksBlockHeight => TypeSignature::UIntType, TenureHeight => TypeSignature::UIntType, BurnBlockHeight => TypeSignature::UIntType, NativeNone => TypeSignature::new_option(no_type()) - .map_err(|_| StaticCheckErrorKind::Expects("Bad construction".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad construction".into()))?, NativeTrue => TypeSignature::BoolType, NativeFalse => TypeSignature::BoolType, TotalLiquidMicroSTX => TypeSignature::UIntType, @@ -1128,7 +1133,9 @@ impl<'a, 'b> TypeChecker<'a, 'b> { } Err(e) => Err(e), })? - .ok_or_else(|| StaticCheckErrorKind::Expects("Expected a depth result".into()))?; + .ok_or_else(|| { + StaticCheckErrorKind::ExpectsRejectable("Expected a depth result".into()) + })?; } runtime_cost(ClarityCostFunction::AnalysisStorage, self, size)?; @@ -1334,7 +1341,7 @@ impl<'a, 'b> TypeChecker<'a, 'b> { )?; if self.function_return_tracker.is_some() { - return Err(StaticCheckErrorKind::Expects( + return Err(StaticCheckErrorKind::ExpectsRejectable( "Interpreter error: Previous function define left dirty typecheck state.".into(), ) .into()); diff --git a/clarity/src/vm/analysis/type_checker/v2_1/natives/assets.rs b/clarity/src/vm/analysis/type_checker/v2_1/natives/assets.rs index c00c7aaeea..b85d1cabff 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/natives/assets.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/natives/assets.rs @@ -237,7 +237,7 @@ pub fn check_special_stx_transfer_memo( let to_type: TypeSignature = TypeSignature::PrincipalType; let memo_type: TypeSignature = TypeSignature::SequenceType(SequenceSubtype::BufferType( BufferLength::try_from(TOKEN_TRANSFER_MEMO_LENGTH as u32) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, )); runtime_cost(ClarityCostFunction::AnalysisTypeLookup, checker, 0)?; diff --git a/clarity/src/vm/analysis/type_checker/v2_1/natives/conversions.rs b/clarity/src/vm/analysis/type_checker/v2_1/natives/conversions.rs index 3ad5fc72b5..a558c6baf5 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/natives/conversions.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/natives/conversions.rs @@ -22,10 +22,9 @@ pub fn check_special_to_consensus_buff( check_argument_count(1, args)?; let input_type = checker.type_check(&args[0], context)?; let buffer_max_len = BufferLength::try_from(input_type.max_serialized_size()?)?; - TypeSignature::new_option(TypeSignature::SequenceType(SequenceSubtype::BufferType( - buffer_max_len, - ))) - .map_err(StaticCheckError::from) + Ok(TypeSignature::new_option(TypeSignature::SequenceType( + SequenceSubtype::BufferType(buffer_max_len), + ))?) } /// `from-consensus-buff?` admits exactly two arguments: @@ -41,7 +40,7 @@ pub fn check_special_from_consensus_buff( check_argument_count(2, args)?; let result_type = TypeSignature::parse_type_repr(StacksEpochId::Epoch21, &args[0], checker)?; checker.type_check_expects(&args[1], context, &TypeSignature::BUFFER_MAX)?; - TypeSignature::new_option(result_type).map_err(StaticCheckError::from) + Ok(TypeSignature::new_option(result_type)?) } /// `to-ascii?` admits exactly one argument, a value to convert to a @@ -97,7 +96,7 @@ pub fn check_special_to_ascii( }; Ok( TypeSignature::new_response(result_type, TypeSignature::UIntType).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FATAL: Legal Clarity response type marked invalid".into(), ) })?, diff --git a/clarity/src/vm/analysis/type_checker/v2_1/natives/mod.rs b/clarity/src/vm/analysis/type_checker/v2_1/natives/mod.rs index 2496897dac..44e6a0d2fe 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/natives/mod.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/natives/mod.rs @@ -104,7 +104,7 @@ fn check_special_list_cons( } let typed_args = result; TypeSignature::parent_list_type(&typed_args) - .map_err(|x| x.into()) + .map_err(StaticCheckError::from) .map(TypeSignature::from) } @@ -431,7 +431,7 @@ fn check_special_equals( // check if there was a least supertype failure. arg_type.ok_or_else(|| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "Arg type should be set because arguments checked for >= 1".into(), ) })??; @@ -456,7 +456,6 @@ fn check_special_if( analysis_typecheck_cost(checker, expr1, expr2)?; TypeSignature::least_supertype(&StacksEpochId::Epoch21, expr1, expr2) - .map_err(StaticCheckErrorKind::from) .and_then(|t| t.concretize()) .map_err(|_| { StaticCheckErrorKind::IfArmsMustMatch(Box::new(expr1.clone()), Box::new(expr2.clone())) @@ -693,7 +692,7 @@ fn check_principal_of( checker.type_check_expects(&args[0], context, &TypeSignature::BUFFER_33)?; Ok( TypeSignature::new_response(TypeSignature::PrincipalType, TypeSignature::UIntType) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, ) } @@ -725,13 +724,13 @@ fn check_principal_construct( ("error_code".into(), TypeSignature::UIntType), ( "value".into(), - TypeSignature::new_option(TypeSignature::PrincipalType).map_err(|_| StaticCheckErrorKind::Expects("FATAL: failed to create (optional principal) type signature".into()))?, + TypeSignature::new_option(TypeSignature::PrincipalType).map_err(|_| StaticCheckErrorKind::ExpectsRejectable("FATAL: failed to create (optional principal) type signature".into()))?, ), ]) - .map_err(|_| StaticCheckErrorKind::Expects("FAIL: PrincipalConstruct failed to initialize type signature".into()))? + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("FAIL: PrincipalConstruct failed to initialize type signature".into()))? .into() ) - .map_err(|_| StaticCheckErrorKind::Expects("FATAL: failed to create `(response principal { error_code: uint, principal: (optional principal) })` type signature".into()))? + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("FATAL: failed to create `(response principal { error_code: uint, principal: (optional principal) })` type signature".into()))? ) } @@ -745,7 +744,7 @@ fn check_secp256k1_recover( checker.type_check_expects(&args[1], context, &TypeSignature::BUFFER_65)?; Ok( TypeSignature::new_response(TypeSignature::BUFFER_33, TypeSignature::UIntType) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, ) } @@ -818,7 +817,9 @@ fn check_get_burn_block_info( Ok(TypeSignature::new_option( block_info_prop.type_result().map_err(|_| { - StaticCheckErrorKind::Expects("FAILED to type valid burn info property".into()) + StaticCheckErrorKind::ExpectsRejectable( + "FAILED to type valid burn info property".into(), + ) })?, )?) } @@ -917,7 +918,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::IntType, ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -928,7 +929,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::UIntType, ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -939,7 +940,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -951,11 +952,11 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::SequenceType(SequenceSubtype::BufferType( BufferLength::try_from(16_u32).map_err(|_| { - StaticCheckErrorKind::Expects("Bad constructor".into()) + StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()) })?, )), ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -968,11 +969,11 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::SequenceType(SequenceSubtype::BufferType( BufferLength::try_from(16_u32).map_err(|_| { - StaticCheckErrorKind::Expects("Bad constructor".into()) + StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()) })?, )), ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1008,7 +1009,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::BoolType, ClarityName::try_from("value".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1061,7 +1062,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("owner".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1073,7 +1074,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("principal".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1092,12 +1093,14 @@ impl TypedNativeFunction { TypeSignature::CONTRACT_NAME_STRING_ASCII_MAX, ) .map_err(|_| { - StaticCheckErrorKind::Expects("Bad constructor".into()) + StaticCheckErrorKind::ExpectsRejectable( + "Bad constructor".into(), + ) })?, ), ]) .map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: PrincipalDestruct failed to initialize type signature" .into(), ) @@ -1113,7 +1116,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("owner".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1124,7 +1127,7 @@ impl TypedNativeFunction { ("unlock-height".into(), TypeSignature::UIntType), ]) .map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: StxGetAccount failed to initialize type signature".into(), ) })? @@ -1135,7 +1138,7 @@ impl TypedNativeFunction { FunctionArg::new( TypeSignature::UIntType, ClarityName::try_from("amount".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1143,7 +1146,7 @@ impl TypedNativeFunction { FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("sender".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1153,7 +1156,7 @@ impl TypedNativeFunction { TypeSignature::BoolType, TypeSignature::UIntType, ) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, }))), StxTransfer => Special(SpecialNativeFunction(&assets::check_special_stx_transfer)), StxTransferMemo => Special(SpecialNativeFunction( @@ -1236,7 +1239,7 @@ impl TypedNativeFunction { args: vec![FunctionArg::new( TypeSignature::PrincipalType, ClarityName::try_from("contract".to_owned()).map_err(|_| { - StaticCheckErrorKind::Expects( + StaticCheckErrorKind::ExpectsRejectable( "FAIL: ClarityName failed to accept default arg name".into(), ) })?, @@ -1245,7 +1248,7 @@ impl TypedNativeFunction { TypeSignature::BUFFER_32, TypeSignature::UIntType, ) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + .map_err(|_| StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()))?, }))), ToAscii => Special(SpecialNativeFunction(&conversions::check_special_to_ascii)), RestrictAssets => Special(SpecialNativeFunction( diff --git a/clarity/src/vm/analysis/type_checker/v2_1/natives/post_conditions.rs b/clarity/src/vm/analysis/type_checker/v2_1/natives/post_conditions.rs index 4288820fd6..5b7547c31f 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/natives/post_conditions.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/natives/post_conditions.rs @@ -86,6 +86,7 @@ pub fn check_restrict_assets( } let ok_type = last_return.ok_or_else(|| StaticCheckErrorKind::CheckerImplementationFailure)?; + Ok(TypeSignature::new_response( ok_type, TypeSignature::UIntType, diff --git a/clarity/src/vm/analysis/type_checker/v2_1/natives/sequences.rs b/clarity/src/vm/analysis/type_checker/v2_1/natives/sequences.rs index ab81b4a5d2..b37ab0999e 100644 --- a/clarity/src/vm/analysis/type_checker/v2_1/natives/sequences.rs +++ b/clarity/src/vm/analysis/type_checker/v2_1/natives/sequences.rs @@ -446,21 +446,23 @@ pub fn check_special_element_at( match collection_type { TypeSignature::SequenceType(ListType(list)) => { let (entry_type, _) = list.destruct(); - TypeSignature::new_option(entry_type).map_err(|e| e.into()) + Ok(TypeSignature::new_option(entry_type)?) } TypeSignature::SequenceType(BufferType(_)) => Ok(TypeSignature::OptionalType(Box::new( TypeSignature::BUFFER_1, ))), TypeSignature::SequenceType(StringType(ASCII(_))) => Ok(TypeSignature::OptionalType( Box::new(TypeSignature::SequenceType(StringType(ASCII( - BufferLength::try_from(1u32) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + BufferLength::try_from(1u32).map_err(|_| { + StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()) + })?, )))), )), TypeSignature::SequenceType(StringType(UTF8(_))) => Ok(TypeSignature::OptionalType( Box::new(TypeSignature::SequenceType(StringType(UTF8( - StringUTF8Length::try_from(1u32) - .map_err(|_| StaticCheckErrorKind::Expects("Bad constructor".into()))?, + StringUTF8Length::try_from(1u32).map_err(|_| { + StaticCheckErrorKind::ExpectsRejectable("Bad constructor".into()) + })?, )))), )), _ => Err(StaticCheckErrorKind::ExpectedSequence(Box::new(collection_type)).into()), @@ -484,7 +486,7 @@ pub fn check_special_index_of( checker.type_check_expects(&args[1], context, &expected_input_type)?; - TypeSignature::new_option(TypeSignature::UIntType).map_err(|e| e.into()) + Ok(TypeSignature::new_option(TypeSignature::UIntType)?) } /// This function type checks the Clarity2 function `slice?`. diff --git a/clarity/src/vm/clarity.rs b/clarity/src/vm/clarity.rs index 7cdb1f2a01..faee73c6fb 100644 --- a/clarity/src/vm/clarity.rs +++ b/clarity/src/vm/clarity.rs @@ -297,15 +297,15 @@ pub trait TransactionConnection: ClarityConnection { let result = db.insert_contract(identifier, contract_analysis); match result { Ok(_) => { - let result = db - .commit() - .map_err(|e| StaticCheckErrorKind::Expects(format!("{e:?}")).into()); + let result = db.commit().map_err(|e| { + StaticCheckErrorKind::ExpectsRejectable(format!("{e:?}")).into() + }); (cost_tracker, result) } Err(e) => { - let result = db - .roll_back() - .map_err(|e| StaticCheckErrorKind::Expects(format!("{e:?}")).into()); + let result = db.roll_back().map_err(|e| { + StaticCheckErrorKind::ExpectsRejectable(format!("{e:?}")).into() + }); if result.is_err() { (cost_tracker, result) } else { diff --git a/clarity/src/vm/contexts.rs b/clarity/src/vm/contexts.rs index b2ea58e5f0..b3cd19f7e3 100644 --- a/clarity/src/vm/contexts.rs +++ b/clarity/src/vm/contexts.rs @@ -1403,7 +1403,11 @@ impl<'a, 'b, 'hooks> Environment<'a, 'b, 'hooks> { self.global_context.begin(); let result = stx_transfer_consolidated(self, from, to, amount, memo); match result { - Ok(value) => match value.clone().expect_result()? { + Ok(value) => match value + .clone() + .expect_result() + .map_err(|_| VmInternalError::Expect("Expected result".into()))? + { Ok(_) => { self.global_context.commit()?; Ok(value) diff --git a/clarity/src/vm/database/clarity_db.rs b/clarity/src/vm/database/clarity_db.rs index 62e4ccd3f9..baab7df387 100644 --- a/clarity/src/vm/database/clarity_db.rs +++ b/clarity/src/vm/database/clarity_db.rs @@ -558,9 +558,13 @@ impl<'a> ClarityDatabase<'a> { if did_sanitize { pre_sanitized_size = Some(value_size); } - sanitized_value.serialize_to_vec()? + sanitized_value + .serialize_to_vec() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))? } else { - value.serialize_to_vec()? + value + .serialize_to_vec() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))? }; let size = serialized.len() as u64; @@ -915,7 +919,11 @@ impl<'a> ClarityDatabase<'a> { "FATAL: failed to load ustx_liquid_supply Clarity key".into(), ) })? - .map(|v| v.value.expect_u128()) + .map(|v| { + v.value + .expect_u128() + .map_err(|_| VmInternalError::Expect("Expected u128".into())) + }) .transpose()? .unwrap_or(0)) } @@ -1452,7 +1460,9 @@ impl ClarityDatabase<'_> { "BUG: failed to decode serialized poison-microblock reporter".into(), ) })?; - let tuple_data = reporter_value.expect_tuple()?; + let tuple_data = reporter_value + .expect_tuple() + .map_err(|_| VmInternalError::Expect("Expected tuple".into()))?; let reporter_value = tuple_data .get("reporter") .map_err(|_| { @@ -1470,8 +1480,12 @@ impl ClarityDatabase<'_> { })? .to_owned(); - let reporter_principal = reporter_value.expect_principal()?; - let seq_u128 = seq_value.expect_u128()?; + let reporter_principal = reporter_value + .expect_principal() + .map_err(|_| VmInternalError::Expect("Expected principal".into()))?; + let seq_u128 = seq_value + .expect_u128() + .map_err(|_| VmInternalError::Expect("Expected u128".into()))?; let seq: u16 = seq_u128 .try_into() @@ -1676,7 +1690,9 @@ impl ClarityDatabase<'_> { Ok(ClarityDatabase::make_key_for_data_map_entry_serialized( contract_identifier, map_name, - &key_value.serialize_to_hex()?, + &key_value + .serialize_to_hex() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?, )) } @@ -1755,7 +1771,9 @@ impl ClarityDatabase<'_> { .into()); } - let key_serialized = key_value.serialize_to_hex()?; + let key_serialized = key_value + .serialize_to_hex() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?; let key = ClarityDatabase::make_key_for_data_map_entry_serialized( contract_identifier, map_name, @@ -1908,7 +1926,9 @@ impl ClarityDatabase<'_> { .into()); } - let key_serialized = key_value.serialize_to_hex()?; + let key_serialized = key_value + .serialize_to_hex() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?; let key_serialized_byte_len = byte_len_of_serialization(&key_serialized); let key = ClarityDatabase::make_key_for_quad( contract_identifier, @@ -1957,7 +1977,9 @@ impl ClarityDatabase<'_> { .into()); } - let key_serialized = key_value.serialize_to_hex()?; + let key_serialized = key_value + .serialize_to_hex() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?; let key_serialized_byte_len = byte_len_of_serialization(&key_serialized); let key = ClarityDatabase::make_key_for_quad( contract_identifier, @@ -2183,7 +2205,9 @@ impl ClarityDatabase<'_> { contract_identifier, StoreType::NonFungibleToken, asset_name, - &asset.serialize_to_hex()?, + &asset + .serialize_to_hex() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?, ); let epoch = self.get_clarity_epoch_version()?; @@ -2194,12 +2218,17 @@ impl ClarityDatabase<'_> { &epoch, )?; let owner = match value { - Some(owner) => owner.value.expect_optional()?, + Some(owner) => owner + .value + .expect_optional() + .map_err(|_| VmInternalError::Expect("Expected an optional".into()))?, None => return Err(RuntimeError::NoSuchToken.into()), }; let principal = match owner { - Some(value) => value.expect_principal()?, + Some(value) => value + .expect_principal() + .map_err(|_| VmInternalError::Expect("Expected principal.".into()))?, None => return Err(RuntimeError::NoSuchToken.into()), }; @@ -2236,7 +2265,9 @@ impl ClarityDatabase<'_> { contract_identifier, StoreType::NonFungibleToken, asset_name, - &asset.serialize_to_hex()?, + &asset + .serialize_to_hex() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?, ); let value = Value::some(Value::Principal(principal.clone()))?; @@ -2265,7 +2296,9 @@ impl ClarityDatabase<'_> { contract_identifier, StoreType::NonFungibleToken, asset_name, - &asset.serialize_to_hex()?, + &asset + .serialize_to_hex() + .map_err(|_| VmInternalError::Expect("IOError filling byte buffer.".into()))?, ); self.put_value(&key, Value::none(), epoch)?; diff --git a/clarity/src/vm/database/key_value_wrapper.rs b/clarity/src/vm/database/key_value_wrapper.rs index 56b082c96f..6c4392aed4 100644 --- a/clarity/src/vm/database/key_value_wrapper.rs +++ b/clarity/src/vm/database/key_value_wrapper.rs @@ -445,7 +445,7 @@ impl RollbackWrapper<'_> { epoch: &StacksEpochId, ) -> Result, SerializationError> { self.stack.last().ok_or_else(|| { - SerializationError::DeserializationError( + SerializationError::DeserializationFailure( "ERROR: Clarity VM attempted GET on non-nested context.".into(), ) })?; @@ -456,7 +456,9 @@ impl RollbackWrapper<'_> { } } let stored_data = self.store.get_data(key).map_err(|_| { - SerializationError::DeserializationError("ERROR: Clarity backing store failure".into()) + SerializationError::DeserializationFailure( + "ERROR: Clarity backing store failure".into(), + ) })?; match stored_data { Some(x) => Ok(Some(Self::deserialize_value(&x, expected, epoch)?)), diff --git a/clarity/src/vm/errors.rs b/clarity/src/vm/errors.rs index 7dd07dcb4f..5d826d8408 100644 --- a/clarity/src/vm/errors.rs +++ b/clarity/src/vm/errors.rs @@ -17,6 +17,7 @@ pub use clarity_types::errors::{ EarlyReturnError, IncomparableError, RuntimeError, VmExecutionError, VmInternalError, }; +pub use clarity_types::ClarityTypeError; pub use crate::vm::analysis::errors::{ check_argument_count, check_arguments_at_least, check_arguments_at_most, CheckErrorKind, diff --git a/clarity/src/vm/functions/assets.rs b/clarity/src/vm/functions/assets.rs index d1f397cfe8..f3f9344f0c 100644 --- a/clarity/src/vm/functions/assets.rs +++ b/clarity/src/vm/functions/assets.rs @@ -137,8 +137,8 @@ pub fn stx_transfer_consolidated( } // loading from/to principals and balances - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; // loading from's locked amount and height // TODO: this does not count the inner stacks block header load, but arguably, // this could be optimized away, so it shouldn't penalize the caller. @@ -240,7 +240,7 @@ pub fn special_stx_account( let v2_unlock_ht = env.global_context.database.get_v2_unlock_height()?; let v3_unlock_ht = env.global_context.database.get_v3_unlock_height()?; - TupleData::from_data(vec![ + Ok(TupleData::from_data(vec![ ( "unlocked" .try_into() @@ -264,7 +264,7 @@ pub fn special_stx_account( ))), ), ]) - .map(Value::Tuple) + .map(Value::Tuple)?) } pub fn special_stx_burn( @@ -288,8 +288,12 @@ pub fn special_stx_burn( return clarity_ecode!(StxErrorCodes::SENDER_IS_NOT_TX_SENDER); } - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(STXBalance::unlocked_and_v1_size as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(STXBalance::unlocked_and_v1_size.try_into().map_err(|_| { + CheckErrorKind::ExpectsRejectable( + "BUG: STXBalance::unlocked_and_v1_size does not fit into a u64".into(), + ) + })?)?; let mut burner_snapshot = env.global_context.database.get_stx_balance_snapshot(from)?; if !burner_snapshot.can_transfer(amount)? { @@ -355,8 +359,8 @@ pub fn special_mint_token( .checked_add(amount) .ok_or_else(|| VmInternalError::Expect("STX overflow".into()))?; - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(TypeSignature::UIntType.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(TypeSignature::UIntType.size()?.into())?; env.global_context.database.set_ft_balance( &env.contract_context.contract_identifier, @@ -422,8 +426,8 @@ pub fn special_mint_asset_v200( Err(e) => Err(e), }?; - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(expected_asset_type.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(expected_asset_type.size()?.into())?; let epoch = *env.epoch(); env.global_context.database.set_nft_owner( @@ -496,7 +500,7 @@ pub fn special_mint_asset_v205( Err(e) => Err(e), }?; - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; env.add_memory(asset_size)?; let epoch = *env.epoch(); @@ -580,8 +584,8 @@ pub fn special_transfer_asset_v200( return clarity_ecode!(TransferAssetErrorCodes::NOT_OWNED_BY); } - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(expected_asset_type.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(expected_asset_type.size()?.into())?; let epoch = *env.epoch(); env.global_context.database.set_nft_owner( @@ -674,7 +678,7 @@ pub fn special_transfer_asset_v205( return clarity_ecode!(TransferAssetErrorCodes::NOT_OWNED_BY); } - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; env.add_memory(asset_size)?; let epoch = *env.epoch(); @@ -772,10 +776,10 @@ pub fn special_transfer_token( .checked_add(amount) .ok_or(RuntimeError::ArithmeticOverflow)?; - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(TypeSignature::UIntType.size()? as u64)?; - env.add_memory(TypeSignature::UIntType.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(TypeSignature::UIntType.size()?.into())?; + env.add_memory(TypeSignature::UIntType.size()?.into())?; env.global_context.database.set_ft_balance( &env.contract_context.contract_identifier, @@ -1011,8 +1015,8 @@ pub fn special_burn_token( }; env.register_ft_burn_event(burner.clone(), amount, asset_identifier)?; - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(TypeSignature::UIntType.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(TypeSignature::UIntType.size()?.into())?; env.global_context.log_token_transfer( burner, @@ -1080,8 +1084,8 @@ pub fn special_burn_asset_v200( return clarity_ecode!(BurnAssetErrorCodes::NOT_OWNED_BY); } - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; - env.add_memory(expected_asset_type.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; + env.add_memory(expected_asset_type.size()?.into())?; let epoch = *env.epoch(); env.global_context.database.burn_nft( @@ -1169,7 +1173,7 @@ pub fn special_burn_asset_v205( return clarity_ecode!(BurnAssetErrorCodes::NOT_OWNED_BY); } - env.add_memory(TypeSignature::PrincipalType.size()? as u64)?; + env.add_memory(TypeSignature::PrincipalType.size()?.into())?; env.add_memory(asset_size)?; let epoch = *env.epoch(); diff --git a/clarity/src/vm/functions/boolean.rs b/clarity/src/vm/functions/boolean.rs index afcd1b88cd..098d0ff575 100644 --- a/clarity/src/vm/functions/boolean.rs +++ b/clarity/src/vm/functions/boolean.rs @@ -22,14 +22,13 @@ use crate::vm::eval; use crate::vm::representations::SymbolicExpression; use crate::vm::types::{TypeSignature, Value}; -fn type_force_bool(value: &Value) -> Result { +fn type_force_bool(value: &Value) -> Result { match *value { Value::Bool(boolean) => Ok(boolean), _ => Err(CheckErrorKind::TypeValueError( Box::new(TypeSignature::BoolType), Box::new(value.clone()), - ) - .into()), + )), } } diff --git a/clarity/src/vm/functions/conversions.rs b/clarity/src/vm/functions/conversions.rs index 4b5438717e..db109e77a8 100644 --- a/clarity/src/vm/functions/conversions.rs +++ b/clarity/src/vm/functions/conversions.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . use clarity_types::types::serialization::SerializationError; +use clarity_types::types::ClarityTypeError; use crate::vm::costs::cost_functions::ClarityCostFunction; use crate::vm::costs::runtime_cost; @@ -130,19 +131,19 @@ pub fn native_buff_to_uint_be(value: Value) -> Result { // either a Int or UInt, depending on the desired result. pub fn native_string_to_int_generic( value: Value, - string_to_value_fn: fn(String) -> Result, + string_to_value_fn: fn(String) -> Result, ) -> Result { match value { Value::Sequence(SequenceData::String(CharType::ASCII(ASCIIData { data }))) => { match String::from_utf8(data) { - Ok(as_string) => string_to_value_fn(as_string), + Ok(as_string) => Ok(string_to_value_fn(as_string)?), Err(_error) => Ok(Value::none()), } } Value::Sequence(SequenceData::String(CharType::UTF8(UTF8Data { data }))) => { let flattened_bytes = data.into_iter().flatten().collect(); match String::from_utf8(flattened_bytes) { - Ok(as_string) => string_to_value_fn(as_string), + Ok(as_string) => Ok(string_to_value_fn(as_string)?), Err(_error) => Ok(Value::none()), } } @@ -157,10 +158,10 @@ pub fn native_string_to_int_generic( } } -fn safe_convert_string_to_int(raw_string: String) -> Result { +fn safe_convert_string_to_int(raw_string: String) -> Result { let possible_int = raw_string.parse::(); match possible_int { - Ok(val) => Value::some(Value::Int(val)), + Ok(val) => Ok(Value::some(Value::Int(val))?), Err(_error) => Ok(Value::none()), } } @@ -169,10 +170,10 @@ pub fn native_string_to_int(value: Value) -> Result { native_string_to_int_generic(value, safe_convert_string_to_int) } -fn safe_convert_string_to_uint(raw_string: String) -> Result { +fn safe_convert_string_to_uint(raw_string: String) -> Result { let possible_int = raw_string.parse::(); match possible_int { - Ok(val) => Value::some(Value::UInt(val)), + Ok(val) => Ok(Value::some(Value::UInt(val))?), Err(_error) => Ok(Value::none()), } } @@ -187,7 +188,7 @@ pub fn native_string_to_uint(value: Value) -> Result { // either an ASCII or UTF8 string, depending on the desired result. pub fn native_int_to_string_generic( value: Value, - bytes_to_value_fn: fn(bytes: Vec) -> Result, + bytes_to_value_fn: fn(bytes: Vec) -> Result, ) -> Result { match value { Value::Int(ref int_value) => { @@ -226,13 +227,13 @@ fn convert_string_to_ascii_ok(s: String) -> Result { let ascii_value = Value::string_ascii_from_bytes(s.into_bytes()).map_err(|_| { VmInternalError::Expect("Unexpected error converting string to ASCII".into()) })?; - Value::okay(ascii_value) + Ok(Value::okay(ascii_value)?) } /// Helper function for UTF8 conversion that can return err u1 for non-ASCII characters fn convert_utf8_to_ascii(s: String) -> Result { match Value::string_ascii_from_bytes(s.into_bytes()) { - Ok(ascii_value) => Value::okay(ascii_value), + Ok(ascii_value) => Ok(Value::okay(ascii_value)?), Err(_) => Ok(Value::err_uint(1)), // Non-ASCII characters in UTF8 } } @@ -264,7 +265,7 @@ pub fn special_to_ascii( // Convert UTF8 to string first, then to ASCII let flattened_bytes: Vec = data.into_iter().flatten().collect(); match String::from_utf8(flattened_bytes) { - Ok(utf8_string) => convert_utf8_to_ascii(utf8_string), + Ok(utf8_string) => Ok(convert_utf8_to_ascii(utf8_string)?), Err(_) => Ok(Value::err_uint(1)), // Invalid UTF8 } } @@ -343,7 +344,7 @@ pub fn from_consensus_buff( ) { Ok(value) => value, Err(SerializationError::UnexpectedSerialization) => { - return Err(CheckErrorKind::Expects("UnexpectedSerialization".into()).into()); + return Err(CheckErrorKind::ExpectsRejectable("UnexpectedSerialization".into()).into()); } Err(_) => return Ok(Value::none()), }; @@ -351,5 +352,5 @@ pub fn from_consensus_buff( return Ok(Value::none()); } - Value::some(result) + Ok(Value::some(result)?) } diff --git a/clarity/src/vm/functions/crypto.rs b/clarity/src/vm/functions/crypto.rs index 7af6ddd13e..499bed5ba3 100644 --- a/clarity/src/vm/functions/crypto.rs +++ b/clarity/src/vm/functions/crypto.rs @@ -46,7 +46,8 @@ macro_rules! native_hash_func { )), }?; let hash = <$module>::from_data(&bytes); - Value::buff_from(hash.as_bytes().to_vec()) + let value = Value::buff_from(hash.as_bytes().to_vec())?; + Ok(value) } }; } diff --git a/clarity/src/vm/functions/database.rs b/clarity/src/vm/functions/database.rs index e32624ef82..2d27491820 100644 --- a/clarity/src/vm/functions/database.rs +++ b/clarity/src/vm/functions/database.rs @@ -76,7 +76,7 @@ pub fn special_contract_call( let mut rest_args_sizes = Vec::with_capacity(rest_args_len); for arg in rest_args_slice.iter() { let evaluated_arg = eval(arg, env, context)?; - rest_args_sizes.push(evaluated_arg.size()? as u64); + rest_args_sizes.push(evaluated_arg.size()?.into()); rest_args.push(SymbolicExpression::atom_value(evaluated_arg)); } @@ -282,7 +282,7 @@ pub fn special_fetch_variable_v205( let result_size = match &result { Ok(data) => data.serialized_byte_len, - Err(_e) => data_types.value_type.size()? as u64, + Err(_e) => data_types.value_type.size()?.into(), }; runtime_cost(ClarityCostFunction::FetchVar, env, result_size)?; @@ -361,7 +361,7 @@ pub fn special_set_variable_v205( let result_size = match &result { Ok(data) => data.serialized_byte_len, - Err(_e) => data_types.value_type.size()? as u64, + Err(_e) => data_types.value_type.size()?.into(), }; runtime_cost(ClarityCostFunction::SetVar, env, result_size)?; @@ -431,7 +431,7 @@ pub fn special_fetch_entry_v205( let result_size = match &result { Ok(data) => data.serialized_byte_len, - Err(_e) => (data_types.value_type.size()? + data_types.key_type.size()?) as u64, + Err(_e) => (data_types.value_type.size()? + data_types.key_type.size()?).into(), }; runtime_cost(ClarityCostFunction::FetchEntry, env, result_size)?; @@ -548,7 +548,7 @@ pub fn special_set_entry_v205( let result_size = match &result { Ok(data) => data.serialized_byte_len, - Err(_e) => (data_types.value_type.size()? + data_types.key_type.size()?) as u64, + Err(_e) => (data_types.value_type.size()? + data_types.key_type.size()?).into(), }; runtime_cost(ClarityCostFunction::SetEntry, env, result_size)?; @@ -635,7 +635,7 @@ pub fn special_insert_entry_v205( let result_size = match &result { Ok(data) => data.serialized_byte_len, - Err(_e) => (data_types.value_type.size()? + data_types.key_type.size()?) as u64, + Err(_e) => (data_types.value_type.size()? + data_types.key_type.size()?).into(), }; runtime_cost(ClarityCostFunction::SetEntry, env, result_size)?; @@ -716,7 +716,7 @@ pub fn special_delete_entry_v205( let result_size = match &result { Ok(data) => data.serialized_byte_len, - Err(_e) => data_types.key_type.size()? as u64, + Err(_e) => data_types.key_type.size()?.into(), }; runtime_cost(ClarityCostFunction::SetEntry, env, result_size)?; @@ -882,7 +882,7 @@ pub fn special_get_block_info( } }; - Value::some(result) + Ok(Value::some(result)?) } /// Handles the `get-burn-block-info?` special function. @@ -941,11 +941,11 @@ pub fn special_get_burn_block_info( .get_burnchain_block_header_hash_for_burnchain_height(height_value)?; match burnchain_header_hash_opt { - Some(burnchain_header_hash) => { - Value::some(Value::Sequence(SequenceData::Buffer(BuffData { + Some(burnchain_header_hash) => Ok(Value::some(Value::Sequence( + SequenceData::Buffer(BuffData { data: burnchain_header_hash.as_bytes().to_vec(), - }))) - } + }), + ))?), None => Ok(Value::none()), } } @@ -1060,7 +1060,7 @@ pub fn special_get_stacks_block_info( } }; - Value::some(result) + Ok(Value::some(result)?) } /// Handles the function `get-tenure-info?` special function. @@ -1173,7 +1173,7 @@ pub fn special_get_tenure_info( } }; - Value::some(result) + Ok(Value::some(result)?) } /// Handles the function `contract-hash?` @@ -1212,5 +1212,7 @@ pub fn special_contract_hash( return Ok(Value::err_uint(2)); }; - Value::okay(Value::buff_from(contract_hash.as_bytes().to_vec())?) + Ok(Value::okay(Value::buff_from( + contract_hash.as_bytes().to_vec(), + )?)?) } diff --git a/clarity/src/vm/functions/define.rs b/clarity/src/vm/functions/define.rs index ba973ad641..e07fcb464b 100644 --- a/clarity/src/vm/functions/define.rs +++ b/clarity/src/vm/functions/define.rs @@ -111,9 +111,9 @@ pub enum DefineResult { fn check_legal_define( name: &str, contract_context: &ContractContext, -) -> Result<(), VmExecutionError> { +) -> Result<(), CheckErrorKind> { if contract_context.is_name_used(name) { - Err(CheckErrorKind::NameAlreadyUsed(name.to_string()).into()) + Err(CheckErrorKind::NameAlreadyUsed(name.to_string())) } else { Ok(()) } diff --git a/clarity/src/vm/functions/options.rs b/clarity/src/vm/functions/options.rs index d8fe8959b0..83722667bd 100644 --- a/clarity/src/vm/functions/options.rs +++ b/clarity/src/vm/functions/options.rs @@ -225,45 +225,45 @@ pub fn special_match( } pub fn native_some(input: Value) -> Result { - Value::some(input) + Ok(Value::some(input)?) } -fn is_some(input: Value) -> Result { +fn is_some(input: Value) -> Result { match input { Value::Optional(ref data) => Ok(data.data.is_some()), - _ => Err(CheckErrorKind::ExpectedOptionalValue(Box::new(input)).into()), + _ => Err(CheckErrorKind::ExpectedOptionalValue(Box::new(input))), } } -fn is_okay(input: Value) -> Result { +fn is_okay(input: Value) -> Result { match input { Value::Response(data) => Ok(data.committed), - _ => Err(CheckErrorKind::ExpectedResponseValue(Box::new(input)).into()), + _ => Err(CheckErrorKind::ExpectedResponseValue(Box::new(input))), } } pub fn native_is_some(input: Value) -> Result { - is_some(input).map(Value::Bool) + Ok(is_some(input).map(Value::Bool)?) } pub fn native_is_none(input: Value) -> Result { - is_some(input).map(|is_some| Value::Bool(!is_some)) + Ok(is_some(input).map(|is_some| Value::Bool(!is_some))?) } pub fn native_is_okay(input: Value) -> Result { - is_okay(input).map(Value::Bool) + Ok(is_okay(input).map(Value::Bool)?) } pub fn native_is_err(input: Value) -> Result { - is_okay(input).map(|is_ok| Value::Bool(!is_ok)) + Ok(is_okay(input).map(|is_ok| Value::Bool(!is_ok))?) } pub fn native_okay(input: Value) -> Result { - Value::okay(input) + Ok(Value::okay(input)?) } pub fn native_error(input: Value) -> Result { - Value::error(input) + Ok(Value::error(input)?) } pub fn native_default_to(default: Value, input: Value) -> Result { diff --git a/clarity/src/vm/functions/post_conditions.rs b/clarity/src/vm/functions/post_conditions.rs index 59a91f9d03..823a25772e 100644 --- a/clarity/src/vm/functions/post_conditions.rs +++ b/clarity/src/vm/functions/post_conditions.rs @@ -114,7 +114,9 @@ fn eval_allowance( return Err(CheckErrorKind::IncorrectArgumentCount(1, rest.len()).into()); } let amount = eval(&rest[0], env, context)?; - let amount = amount.expect_u128()?; + let amount = amount + .expect_u128() + .map_err(|_| VmInternalError::Expect("Expected u128".into()))?; Ok(Allowance::Stx(StxAllowance { amount })) } NativeFunctions::AllowanceWithFt => { @@ -123,7 +125,10 @@ fn eval_allowance( } let contract_value = eval(&rest[0], env, context)?; - let contract = contract_value.clone().expect_principal()?; + let contract = contract_value + .clone() + .expect_principal() + .map_err(|_| VmInternalError::Expect("Expected principal".into()))?; let contract_identifier = match contract { PrincipalData::Standard(_) => { return Err(CheckErrorKind::ExpectedContractPrincipalValue( @@ -135,7 +140,11 @@ fn eval_allowance( }; let asset_name = eval(&rest[1], env, context)?; - let asset_name = asset_name.expect_string_ascii()?.as_str().into(); + let asset_name = asset_name + .expect_string_ascii() + .map_err(|_| VmInternalError::Expect("Expected ASCII String.".into()))? + .as_str() + .into(); let asset = AssetIdentifier { contract_identifier, @@ -143,7 +152,9 @@ fn eval_allowance( }; let amount = eval(&rest[2], env, context)?; - let amount = amount.expect_u128()?; + let amount = amount + .expect_u128() + .map_err(|_| VmInternalError::Expect("Expected u128".into()))?; Ok(Allowance::Ft(FtAllowance { asset, amount })) } @@ -153,7 +164,10 @@ fn eval_allowance( } let contract_value = eval(&rest[0], env, context)?; - let contract = contract_value.clone().expect_principal()?; + let contract = contract_value + .clone() + .expect_principal() + .map_err(|_| VmInternalError::Expect("Expected principal".into()))?; let contract_identifier = match contract { PrincipalData::Standard(_) => { return Err(CheckErrorKind::ExpectedContractPrincipalValue( @@ -165,7 +179,11 @@ fn eval_allowance( }; let asset_name = eval(&rest[1], env, context)?; - let asset_name = asset_name.expect_string_ascii()?.as_str().into(); + let asset_name = asset_name + .expect_string_ascii() + .map_err(|_| VmInternalError::Expect("Expected ASCII String.".into()))? + .as_str() + .into(); let asset = AssetIdentifier { contract_identifier, @@ -173,7 +191,9 @@ fn eval_allowance( }; let asset_id_list = eval(&rest[2], env, context)?; - let asset_ids = asset_id_list.expect_list()?; + let asset_ids = asset_id_list + .expect_list() + .map_err(|_| VmInternalError::Expect("Expected list".into()))?; Ok(Allowance::Nft(NftAllowance { asset, asset_ids })) } @@ -182,7 +202,9 @@ fn eval_allowance( return Err(CheckErrorKind::IncorrectArgumentCount(1, rest.len()).into()); } let amount = eval(&rest[0], env, context)?; - let amount = amount.expect_u128()?; + let amount = amount + .expect_u128() + .map_err(|_| VmInternalError::Expect("Expected u128".into()))?; Ok(Allowance::Stacking(StackingAllowance { amount })) } NativeFunctions::AllowanceAll => { @@ -217,7 +239,9 @@ pub fn special_restrict_assets( let body_exprs = &args[2..]; let asset_owner = eval(asset_owner_expr, env, context)?; - let asset_owner = asset_owner.expect_principal()?; + let asset_owner = asset_owner + .expect_principal() + .map_err(|_| VmInternalError::Expect("Expected principal".into()))?; runtime_cost( ClarityCostFunction::RestrictAssets, @@ -258,7 +282,7 @@ pub fn special_restrict_assets( Ok(None) => {} Ok(Some(violation_index)) => { env.global_context.roll_back()?; - return Value::error(Value::UInt(violation_index)); + return Ok(Value::error(Value::UInt(violation_index))?); } Err(e) => { env.global_context.roll_back()?; @@ -272,7 +296,7 @@ pub fn special_restrict_assets( match eval_result { Ok(Some(last)) => { // body completed successfully — commit and return ok(last) - Value::okay(last) + Ok(Value::okay(last)?) } Ok(None) => { // Body had no expressions (shouldn't happen due to argument checks) @@ -352,7 +376,7 @@ pub fn special_as_contract( Ok(None) => {} Ok(Some(violation_index)) => { nested_env.global_context.roll_back()?; - return Value::error(Value::UInt(violation_index)); + return Ok(Value::error(Value::UInt(violation_index))?); } Err(e) => { nested_env.global_context.roll_back()?; @@ -366,7 +390,7 @@ pub fn special_as_contract( match eval_result { Ok(Some(last)) => { // body completed successfully — commit and return ok(last) - Value::okay(last) + Ok(Value::okay(last)?) } Ok(None) => { // Body had no expressions (shouldn't happen due to argument checks) diff --git a/clarity/src/vm/functions/sequences.rs b/clarity/src/vm/functions/sequences.rs index a30ce53a41..66f0ed0eca 100644 --- a/clarity/src/vm/functions/sequences.rs +++ b/clarity/src/vm/functions/sequences.rs @@ -16,12 +16,13 @@ use std::cmp; +use clarity_types::errors::VmInternalError; use stacks_common::types::StacksEpochId; use crate::vm::costs::cost_functions::ClarityCostFunction; use crate::vm::costs::{runtime_cost, CostOverflowingMath}; use crate::vm::errors::{ - check_argument_count, check_arguments_at_least, CheckErrorKind, RuntimeError, VmExecutionError, + check_argument_count, check_arguments_at_least, CheckErrorKind, VmExecutionError, }; use crate::vm::representations::SymbolicExpression; use crate::vm::types::signatures::ListTypeData; @@ -45,7 +46,8 @@ pub fn list_cons( runtime_cost(ClarityCostFunction::ListCons, env, arg_size)?; - Value::cons_list(args, env.epoch()) + let value = Value::cons_list(args, env.epoch())?; + Ok(value) } pub fn special_filter( @@ -178,7 +180,8 @@ pub fn special_map( mapped_results.push(res); } - Value::cons_list(mapped_results, env.epoch()) + let value = Value::cons_list(mapped_results, env.epoch())?; + Ok(value) } pub fn special_append( @@ -205,7 +208,7 @@ pub fn special_append( )?; if entry_type.is_no_type() { assert_eq!(size, 0); - return Value::cons_list(vec![element], env.epoch()); + return Ok(Value::cons_list(vec![element], env.epoch())?); } if let Ok(next_entry_type) = TypeSignature::least_supertype(env.epoch(), &entry_type, &element_type) @@ -247,10 +250,17 @@ pub fn special_concat_v200( match (&mut wrapped_seq, other_wrapped_seq) { (Value::Sequence(ref mut seq), Value::Sequence(other_seq)) => { - seq.concat(env.epoch(), other_seq) + seq.concat(env.epoch(), other_seq)? } - _ => Err(RuntimeError::BadTypeConstruction.into()), - }?; + (Value::Sequence(ref mut seq_data), other_value) => { + return Err(CheckErrorKind::TypeValueError( + Box::new(seq_data.type_signature()?), + Box::new(other_value), + ) + .into()) + } + _ => return Err(CheckErrorKind::ExpectedSequence(Box::new(TypeSignature::NoType)).into()), + }; Ok(wrapped_seq) } @@ -273,13 +283,21 @@ pub fn special_concat_v205( (seq.len() as u64).cost_overflow_add(other_seq.len() as u64)?, )?; - seq.concat(env.epoch(), other_seq) + seq.concat(env.epoch(), other_seq)? + } + (Value::Sequence(ref mut seq_data), other_value) => { + runtime_cost(ClarityCostFunction::Concat, env, 1)?; + return Err(CheckErrorKind::TypeValueError( + Box::new(seq_data.type_signature()?), + Box::new(other_value), + ) + .into()); } _ => { runtime_cost(ClarityCostFunction::Concat, env, 1)?; - Err(RuntimeError::BadTypeConstruction.into()) + return Err(CheckErrorKind::ExpectedSequence(Box::new(TypeSignature::NoType)).into()); } - }?; + }; Ok(wrapped_seq) } @@ -335,7 +353,7 @@ pub fn native_len(sequence: Value) -> Result { pub fn native_index_of(sequence: Value, to_find: Value) -> Result { if let Value::Sequence(sequence_data) = sequence { match sequence_data.contains(to_find)? { - Some(index) => Value::some(Value::UInt(index as u128)), + Some(index) => Ok(Value::some(Value::UInt(index as u128))?), None => Ok(Value::none()), } } else { @@ -366,8 +384,10 @@ pub fn native_element_at(sequence: Value, index: Value) -> Result { + // TODO: REMOVE THIS COMMENT: Is it better to keep RuntimeError::BadTypeConstruction? It just seems weird + // to have this error. if we are going to have an error for htings that shouldn't really hit, I feel like + // it should be more explicit like an ExpectAcceptable(String) error here instead. But handling explicitly + // for now until I get feedback. + + // These errors should not really ever occur at runtime. These should have already been caught + // during static analysis. However, handle them just in case. + + // seq must be a sequence + if !matches!(seq, Value::Sequence(_)) { + return Err(CheckErrorKind::ExpectedSequence(Box::new( + TypeSignature::NoType, + ))); + } + + // left must be uint + if !matches!(left_position, Value::UInt(_)) { + return Err(CheckErrorKind::TypeValueError( + Box::new(TypeSignature::UIntType), + Box::new(left_position), + )); + } + + // right must be uint + Err(CheckErrorKind::TypeValueError( + Box::new(TypeSignature::UIntType), + Box::new(right_position), + )) } - _ => Err(RuntimeError::BadTypeConstruction.into()), } })(); @@ -419,7 +468,7 @@ pub fn special_slice( Ok(sliced_seq) => Ok(sliced_seq), Err(e) => { runtime_cost(ClarityCostFunction::Slice, env, 0)?; - Err(e) + Err(e.into()) } } } @@ -469,13 +518,12 @@ pub fn special_replace_at( .into()); }; - if let Value::Sequence(data) = seq { - let seq_len = data.len(); - if index >= seq_len { - return Ok(Value::none()); - } - data.replace_at(env.epoch(), index, new_element) - } else { - Err(CheckErrorKind::ExpectedSequence(Box::new(seq_type)).into()) + let Value::Sequence(data) = seq else { + return Err(CheckErrorKind::ExpectedSequence(Box::new(seq_type)).into()); + }; + let seq_len = data.len(); + if index >= seq_len { + return Ok(Value::none()); } + Ok(data.replace_at(env.epoch(), index, new_element)?) } diff --git a/clarity/src/vm/functions/tuples.rs b/clarity/src/vm/functions/tuples.rs index 87b8eb7281..4a177424fc 100644 --- a/clarity/src/vm/functions/tuples.rs +++ b/clarity/src/vm/functions/tuples.rs @@ -37,7 +37,7 @@ pub fn tuple_cons( let bindings = parse_eval_bindings(args, SyntaxBindingErrorType::TupleCons, env, context)?; runtime_cost(ClarityCostFunction::TupleCons, env, bindings.len())?; - TupleData::from_data(bindings).map(Value::from) + Ok(TupleData::from_data(bindings).map(Value::from)?) } pub fn tuple_get( @@ -76,7 +76,7 @@ pub fn tuple_get( } Value::Tuple(tuple_data) => { runtime_cost(ClarityCostFunction::TupleGet, env, tuple_data.len())?; - tuple_data.get_owned(arg_name) + Ok(tuple_data.get_owned(arg_name)?) } _ => Err(CheckErrorKind::ExpectedTuple(Box::new(TypeSignature::type_of(&value)?)).into()), } diff --git a/clarity/src/vm/mod.rs b/clarity/src/vm/mod.rs index 9a3799f92f..b749052ac9 100644 --- a/clarity/src/vm/mod.rs +++ b/clarity/src/vm/mod.rs @@ -421,9 +421,9 @@ pub fn eval_all( contract_context.persisted_names.insert(name.clone()); global_context.add_memory(value_type.type_size() - .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))? as u64)?; + .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))?.into())?; - global_context.add_memory(value.size()? as u64)?; + global_context.add_memory(value.size()?.into())?; let data_type = global_context.database.create_variable(&contract_context.contract_identifier, &name, value_type)?; global_context.database.set_variable(&contract_context.contract_identifier, &name, value, &data_type, &global_context.epoch_id)?; @@ -437,9 +437,9 @@ pub fn eval_all( contract_context.persisted_names.insert(name.clone()); global_context.add_memory(key_type.type_size() - .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))? as u64)?; + .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))?.into())?; global_context.add_memory(value_type.type_size() - .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))? as u64)?; + .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))?.into())?; let data_type = global_context.database.create_map(&contract_context.contract_identifier, &name, key_type, value_type)?; @@ -450,7 +450,7 @@ pub fn eval_all( contract_context.persisted_names.insert(name.clone()); global_context.add_memory(TypeSignature::UIntType.type_size() - .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))? as u64)?; + .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))?.into())?; let data_type = global_context.database.create_fungible_token(&contract_context.contract_identifier, &name, &total_supply)?; @@ -461,7 +461,7 @@ pub fn eval_all( contract_context.persisted_names.insert(name.clone()); global_context.add_memory(asset_type.type_size() - .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))? as u64)?; + .map_err(|_| VmInternalError::Expect("Type size should be realizable".into()))?.into())?; let data_type = global_context.database.create_non_fungible_token(&contract_context.contract_identifier, &name, &asset_type)?; diff --git a/clarity/src/vm/tests/datamaps.rs b/clarity/src/vm/tests/datamaps.rs index ae71745a30..3de8e8f557 100644 --- a/clarity/src/vm/tests/datamaps.rs +++ b/clarity/src/vm/tests/datamaps.rs @@ -13,15 +13,19 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clarity_types::types::ClarityTypeError; +#[cfg(test)] +use clarity_types::VmExecutionError; + use crate::vm::types::{TupleData, Value}; #[cfg(test)] use crate::vm::{ errors::{CheckErrorKind, EarlyReturnError, SyntaxBindingError}, types::{ListData, SequenceData, TupleTypeSignature, TypeSignature}, }; -use crate::vm::{execute, ClarityName, VmExecutionError}; +use crate::vm::{execute, ClarityName}; -fn assert_executes(expected: Result, input: &str) { +fn assert_executes(expected: Result, input: &str) { assert_eq!(expected.unwrap(), execute(input).unwrap().unwrap()); } diff --git a/clarity/src/vm/tests/representations.rs b/clarity/src/vm/tests/representations.rs index e96db9ba73..6bd017efa7 100644 --- a/clarity/src/vm/tests/representations.rs +++ b/clarity/src/vm/tests/representations.rs @@ -13,11 +13,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clarity_types::types::ClarityTypeError; use proptest::prelude::*; use proptest::string::string_regex; use stacks_common::codec::StacksMessageCodec; -use crate::vm::errors::RuntimeError; use crate::vm::representations::{ CLARITY_NAME_REGEX_STRING, CONTRACT_MAX_NAME_LENGTH, CONTRACT_MIN_NAME_LENGTH, CONTRACT_NAME_REGEX_STRING, MAX_STRING_LEN, @@ -161,11 +161,11 @@ fn any_invalid_clarity_name() -> impl Strategy { fn prop_clarity_name_invalid_patterns() { proptest!(|(name in any_invalid_clarity_name())| { let result = ClarityName::try_from(name.clone()); - prop_assert!(result.is_err(), "Expected invalid name '{}' to be rejected", name); + prop_assert!(result.is_err(), "Expected invalid name '{name}' to be rejected"); prop_assert!(matches!( result.unwrap_err(), - RuntimeError::BadNameValue(_, _) - ), "Expected BadNameValue error for invalid name '{}'", name); + ClarityTypeError::InvalidClarityName(_) + ), "Expected BadNameValue error for invalid name '{name}'"); }); } @@ -299,11 +299,11 @@ fn any_invalid_contract_name() -> impl Strategy { fn prop_contract_name_invalid_patterns() { proptest!(|(name in any_invalid_contract_name())| { let result = ContractName::try_from(name.clone()); - prop_assert!(result.is_err(), "Expected invalid contract name '{}' to be rejected", name); + prop_assert!(result.is_err(), "Expected invalid contract name '{name}' to be rejected"); prop_assert!(matches!( result.unwrap_err(), - RuntimeError::BadNameValue(_, _) - ), "Expected BadNameValue error for invalid contract name '{}'", name); + ClarityTypeError::InvalidContractName(_) + ), "Expected BadNameValue error for invalid contract name '{name}'"); }); } diff --git a/clarity/src/vm/tests/sequences.rs b/clarity/src/vm/tests/sequences.rs index 5796d258c9..4e26dadf57 100644 --- a/clarity/src/vm/tests/sequences.rs +++ b/clarity/src/vm/tests/sequences.rs @@ -22,14 +22,14 @@ use stacks_common::types::StacksEpochId; use crate::vm::tests::test_clarity_versions; #[cfg(test)] use crate::vm::{ - errors::{CheckErrorKind, RuntimeError, VmExecutionError}, + errors::{CheckErrorKind, VmExecutionError}, execute, execute_v2, types::{ signatures::{ SequenceSubtype::{self, BufferType, StringType}, StringSubtype::ASCII, }, - BufferLength, StringSubtype, StringUTF8Length, + BufferLength, ListTypeData, StringSubtype, StringUTF8Length, TypeSignature::{self, BoolType, IntType, SequenceType, UIntType}, Value, }, @@ -552,6 +552,24 @@ fn test_slice_utf8() { } } +#[test] +fn test_slice_type_errors() { + assert_eq!( + execute_v2("(slice? 3 u0 u1)").unwrap_err(), + CheckErrorKind::ExpectedSequence(Box::new(TypeSignature::NoType)).into() + ); + + assert_eq!( + execute_v2("(slice? (list 1 2 3) 0 u1)").unwrap_err(), + CheckErrorKind::TypeValueError(Box::new(UIntType), Box::new(Value::Int(0))).into() + ); + + assert_eq!( + execute_v2("(slice? (list 1 2 3) u0 1)").unwrap_err(), + CheckErrorKind::TypeValueError(Box::new(UIntType), Box::new(Value::Int(1))).into() + ); +} + #[test] fn test_simple_list_concat() { let tests = [ @@ -587,17 +605,33 @@ fn test_simple_list_concat() { assert_eq!( execute("(concat (list 1) (list u4 u8))").unwrap_err(), - CheckErrorKind::TypeError(Box::new(IntType), Box::new(UIntType)).into() + CheckErrorKind::TypeError( + Box::new(TypeSignature::IntType), + Box::new(TypeSignature::UIntType) + ) + .into() ); assert_eq!( execute("(concat (list 1) 3)").unwrap_err(), - RuntimeError::BadTypeConstruction.into() + CheckErrorKind::TypeValueError( + Box::new(SequenceType(SequenceSubtype::ListType( + ListTypeData::new_list(TypeSignature::IntType, 1).unwrap() + ))), + Box::new(Value::Int(3)) + ) + .into() ); assert_eq!( execute("(concat (list 1) \"1\")").unwrap_err(), - RuntimeError::BadTypeConstruction.into() + CheckErrorKind::TypeError( + Box::new(SequenceType(SequenceSubtype::ListType( + ListTypeData::new_list(TypeSignature::IntType, 1).unwrap() + ))), + Box::new(TypeSignature::STRING_ASCII_MIN) + ) + .into() ); } @@ -623,12 +657,22 @@ fn test_simple_buff_concat() { assert_eq!( execute("(concat 0x31 3)").unwrap_err(), - RuntimeError::BadTypeConstruction.into() + CheckErrorKind::TypeValueError( + Box::new(TypeSignature::BUFFER_MIN), + Box::new(Value::Int(3)) + ) + .into() ); assert_eq!( execute("(concat 0x31 (list 1))").unwrap_err(), - RuntimeError::BadTypeConstruction.into() + CheckErrorKind::TypeError( + Box::new(TypeSignature::BUFFER_MIN), + Box::new(TypeSignature::SequenceType(SequenceSubtype::ListType( + ListTypeData::new_list(TypeSignature::IntType, 1).unwrap() + ))) + ) + .into() ); } diff --git a/clarity/src/vm/types/mod.rs b/clarity/src/vm/types/mod.rs index 3ef4d04b73..f70d3e6143 100644 --- a/clarity/src/vm/types/mod.rs +++ b/clarity/src/vm/types/mod.rs @@ -103,7 +103,7 @@ impl BurnBlockInfoProperty { ("hashbytes".into(), TypeSignature::BUFFER_32), ]) .map_err(|_| { - CheckErrorKind::Expects( + CheckErrorKind::ExpectsRejectable( "FATAL: bad type signature for pox addr".into(), ) })?, @@ -111,12 +111,14 @@ impl BurnBlockInfoProperty { 2, ) .map_err(|_| { - CheckErrorKind::Expects("FATAL: bad list type signature".into()) + CheckErrorKind::ExpectsRejectable("FATAL: bad list type signature".into()) })?, ), ("payout".into(), TypeSignature::UIntType), ]) - .map_err(|_| CheckErrorKind::Expects("FATAL: bad type signature for pox addr".into()))? + .map_err(|_| { + CheckErrorKind::ExpectsRejectable("FATAL: bad type signature for pox addr".into()) + })? .into(), }; Ok(result) diff --git a/clarity/src/vm/types/signatures.rs b/clarity/src/vm/types/signatures.rs index 4f9dca8740..3ba942537b 100644 --- a/clarity/src/vm/types/signatures.rs +++ b/clarity/src/vm/types/signatures.rs @@ -236,7 +236,7 @@ impl TypeSignatureExt for TypeSignature { let entry_type = TypeSignature::parse_type_repr(epoch, atomic_type_arg, accounting)?; let max_len = u32::try_from(*max_len).map_err(|_| CommonCheckErrorKind::ValueTooLarge)?; - ListTypeData::new_list(entry_type, max_len).map(|x| x.into()) + Ok(ListTypeData::new_list(entry_type, max_len)?.into()) } else { Err(CommonCheckErrorKind::InvalidTypeDescription) } @@ -268,8 +268,9 @@ impl TypeSignatureExt for TypeSignature { return Err(CommonCheckErrorKind::InvalidTypeDescription); } if let SymbolicExpressionType::LiteralValue(Value::Int(buff_len)) = &type_args[0].expr { - BufferLength::try_from(*buff_len) - .map(|buff_len| SequenceType(SequenceSubtype::BufferType(buff_len))) + Ok(SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(*buff_len)?, + ))) } else { Err(CommonCheckErrorKind::InvalidTypeDescription) } @@ -284,9 +285,9 @@ impl TypeSignatureExt for TypeSignature { return Err(CommonCheckErrorKind::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))) - }) + Ok(SequenceType(SequenceSubtype::StringType( + StringSubtype::UTF8(StringUTF8Length::try_from(*utf8_len)?), + ))) } else { Err(CommonCheckErrorKind::InvalidTypeDescription) } @@ -301,9 +302,9 @@ impl TypeSignatureExt for TypeSignature { return Err(CommonCheckErrorKind::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))) - }) + Ok(SequenceType(SequenceSubtype::StringType( + StringSubtype::ASCII(BufferLength::try_from(*buff_len)?), + ))) } else { Err(CommonCheckErrorKind::InvalidTypeDescription) } @@ -319,7 +320,7 @@ impl TypeSignatureExt for TypeSignature { } let inner_type = TypeSignature::parse_type_repr(epoch, &type_args[0], accounting)?; - TypeSignature::new_option(inner_type) + Ok(TypeSignature::new_option(inner_type)?) } fn parse_response_type_repr( @@ -648,6 +649,7 @@ impl fmt::Display for FunctionArg { mod test { use clarity_types::errors::CheckErrorKind; use clarity_types::errors::CheckErrorKind::*; + use clarity_types::types::ClarityTypeError; #[cfg(test)] use rstest::rstest; #[cfg(test)] @@ -698,7 +700,7 @@ mod test { assert_eq!( TupleTypeSignature::try_from(keys).unwrap_err(), - CommonCheckErrorKind::ValueTooLarge + ClarityTypeError::ValueTooLarge ); } diff --git a/contrib/stacks-cli/Cargo.toml b/contrib/stacks-cli/Cargo.toml index a942aa50c3..93c0bfed19 100644 --- a/contrib/stacks-cli/Cargo.toml +++ b/contrib/stacks-cli/Cargo.toml @@ -9,6 +9,7 @@ clarity = { path = "../../clarity", default-features = false } clarity-cli = { path = "../clarity-cli", default-features = false } stacks-common = { path = "../../stacks-common", default-features = false } serde_json = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] stacks-common = { path = "../../stacks-common", default-features = false, features = ["testing"] } diff --git a/contrib/stacks-cli/src/main.rs b/contrib/stacks-cli/src/main.rs index 2014be553f..7d10b7b5a5 100644 --- a/contrib/stacks-cli/src/main.rs +++ b/contrib/stacks-cli/src/main.rs @@ -22,7 +22,7 @@ use std::io::Read; use std::io::prelude::*; use std::{env, fs, io}; -use clarity::vm::errors::{RuntimeError, VmExecutionError}; +use clarity::vm::errors::{ClarityTypeError, VmExecutionError}; use clarity::vm::types::PrincipalData; use clarity::vm::{ClarityName, ClarityVersion, ContractName, Value}; use clarity_cli::vm_execute; @@ -182,55 +182,25 @@ raw binary microblocks will be read from stdin. N.B. Stacks microblocks are not stored as files in the Stacks chainstate -- they are stored in block's sqlite database."; -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] enum CliError { - ClarityRuntimeError(RuntimeError), - ClarityGeneralError(VmExecutionError), + #[error("Clarity error: {0}")] + ClarityGeneralError(#[from] VmExecutionError), + #[error("Clarity error: {0}")] + ClarityTypeError(#[from] ClarityTypeError), + #[error("{0}")] Message(String), + #[error("{USAGE}")] Usage, + #[error("Invalid chain ID: {0}")] InvalidChainId(std::num::ParseIntError), } -impl std::error::Error for CliError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - CliError::ClarityRuntimeError(e) => Some(e), - CliError::ClarityGeneralError(e) => Some(e), - _ => None, - } - } -} - -impl std::fmt::Display for CliError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CliError::ClarityRuntimeError(e) => write!(f, "Clarity error: {e:?}"), - CliError::ClarityGeneralError(e) => write!(f, "Clarity error: {e}"), - CliError::Message(e) => write!(f, "{e}"), - CliError::Usage => write!(f, "{USAGE}"), - CliError::InvalidChainId(e) => write!(f, "Invalid chain ID: {e}"), - } - } -} - impl From<&str> for CliError { fn from(value: &str) -> Self { CliError::Message(value.into()) } } - -impl From for CliError { - fn from(value: RuntimeError) -> Self { - CliError::ClarityRuntimeError(value) - } -} - -impl From for CliError { - fn from(value: VmExecutionError) -> Self { - CliError::ClarityGeneralError(value) - } -} - impl From for CliError { fn from(value: NetError) -> Self { CliError::Message(format!("Stacks NetError: {value}")) @@ -1628,7 +1598,7 @@ mod test { let result = main_handler(to_string_vec(&cc_args)); assert!(result.is_err(), "Result should be err!"); - let expected_msg = "Failed to deserialize: Deserialization error: Bad hex string"; + let expected_msg = "Failed to deserialize: Deserialization failure: Bad hex string"; assert_eq!(expected_msg, result.unwrap_err().to_string()); } diff --git a/stacks-common/src/util/macros.rs b/stacks-common/src/util/macros.rs index cbf602cfa1..57b1fa5f8f 100644 --- a/stacks-common/src/util/macros.rs +++ b/stacks-common/src/util/macros.rs @@ -204,19 +204,19 @@ macro_rules! define_versioned_named_enum_internal { #[allow(clippy::crate_in_macro_def)] #[macro_export] macro_rules! guarded_string { - ($Name:ident, $Label:literal, $Regex:expr, $MaxStringLength:expr, $ErrorType:ty, $ErrorVariant:path) => { + ($Name:ident, $Regex:expr, $MaxStringLength:expr, $ErrorType:ty, $ErrorVariant:path) => { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct $Name(String); impl TryFrom for $Name { type Error = $ErrorType; fn try_from(value: String) -> Result { if value.len() > ($MaxStringLength as usize) { - return Err($ErrorVariant($Label, value)); + return Err($ErrorVariant(value)); } if $Regex.is_match(&value) { Ok(Self(value)) } else { - Err($ErrorVariant($Label, value)) + Err($ErrorVariant(value)) } } } @@ -426,8 +426,8 @@ macro_rules! impl_array_newtype { } } + #[allow(clippy::non_canonical_clone_impl)] impl Clone for $thing { - #[allow(clippy::non_canonical_clone_impl)] fn clone(&self) -> Self { $thing(self.0.clone()) } diff --git a/stacks-node/src/tests/signer/commands/block_wait.rs b/stacks-node/src/tests/signer/commands/block_wait.rs index 04e9b9f2b7..7706055a43 100644 --- a/stacks-node/src/tests/signer/commands/block_wait.rs +++ b/stacks-node/src/tests/signer/commands/block_wait.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use libsigner::v0::messages::RejectReason; use madhouse::{Command, CommandWrapper}; use proptest::prelude::{Just, Strategy}; -use proptest::prop_oneof; use stacks::chainstate::stacks::{TenureChangeCause, TenureChangePayload, TransactionPayload}; use super::context::{SignerTestContext, SignerTestState}; diff --git a/stacks-signer/src/client/mod.rs b/stacks-signer/src/client/mod.rs index 9c893237be..483b7ec7bd 100644 --- a/stacks-signer/src/client/mod.rs +++ b/stacks-signer/src/client/mod.rs @@ -21,7 +21,7 @@ pub(crate) mod stacks_client; use std::time::Duration; -use clarity::vm::errors::VmExecutionError; +use clarity::vm::errors::ClarityTypeError; use clarity::vm::types::serialization::SerializationError; use libsigner::RPCError; use libstackerdb::Error as StackerDBError; @@ -79,9 +79,9 @@ pub enum ClientError { /// Not connected #[error("Not connected")] NotConnected, - /// Clarity interpreter error - #[error("Clarity interpreter error: {0}")] - ClarityError(#[from] VmExecutionError), + /// Clarity type error + #[error("Clarity error: {0}")] + ClarityError(#[from] ClarityTypeError), /// Malformed reward set #[error("Malformed contract data: {0}")] MalformedContractData(String), diff --git a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs index a154c33062..e7eb8a4548 100644 --- a/stackslib/src/chainstate/nakamoto/coordinator/mod.rs +++ b/stackslib/src/chainstate/nakamoto/coordinator/mod.rs @@ -109,16 +109,16 @@ impl OnChainRewardSetProvider<'_, T> { sortdb, block_id, SIGNERS_NAME, - &format!("(map-get? cycle-set-height u{})", cycle), + &format!("(map-get? cycle-set-height u{cycle})"), )? .expect_optional() - .map_err(|e| Error::ChainstateError(e.into()))? + .map_err(ChainstateError::from)? .map(|x| { let as_u128 = x.expect_u128()?; - Ok(u64::try_from(as_u128).expect("FATAL: block height exceeded u64")) + u64::try_from(as_u128) + .map_err(|_| ChainstateError::Expects("block height exceeded u64".into())) }) - .transpose() - .map_err(|e| Error::ChainstateError(ChainstateError::ClarityError(e)))? + .transpose()? else { err_or_debug!( debug_log, @@ -156,13 +156,13 @@ impl OnChainRewardSetProvider<'_, T> { ) .map_err(ChainstateError::ClarityError)? .expect_optional() - .map_err(|e| Error::ChainstateError(e.into()))? + .map_err(ChainstateError::from)? .map(|x| { let as_u128 = x.expect_u128()?; - Ok(u64::try_from(as_u128).expect("FATAL: block height exceeded u64")) + u64::try_from(as_u128) + .map_err(|_| ChainstateError::Expects("block height exceeded u64".into())) }) - .transpose() - .map_err(|e| Error::ChainstateError(ChainstateError::ClarityError(e)))? + .transpose()? else { error!( "The reward set was not written to .signers before it was needed by Nakamoto"; @@ -298,7 +298,9 @@ pub fn get_nakamoto_reward_cycle_info( let burn_height = burnchain.nakamoto_first_block_of_cycle(reward_cycle); let epoch_at_height = SortitionDB::get_stacks_epoch(sort_db.conn(), burn_height)? - .unwrap_or_else(|| panic!("FATAL: no epoch defined for burn height {}", burn_height)) + .ok_or_else(|| { + ChainstateError::Expects(format!("no epoch defined for burn height {burn_height}")) + })? .epoch_id; assert!( @@ -366,9 +368,13 @@ pub fn load_nakamoto_reward_set( let prepare_phase_start_height = cycle_start_height.saturating_sub(u64::from(burnchain.pox_constants.prepare_length)); let epoch_at_height = - SortitionDB::get_stacks_epoch(sort_db.conn(), prepare_phase_start_height)?.unwrap_or_else( - || panic!("FATAL: no epoch defined for burn height {prepare_phase_start_height}"), - ); + SortitionDB::get_stacks_epoch(sort_db.conn(), prepare_phase_start_height)?.ok_or_else( + || { + ChainstateError::Expects(format!( + "FATAL: no epoch defined for burn height {prepare_phase_start_height}" + )) + }, + )?; if epoch_at_height.epoch_id < StacksEpochId::Epoch30 { // in epoch 2.5, and in the first reward cycle of epoch 3.0, the reward set can *only* be found in the sortition DB. // The nakamoto chain-processing rules aren't active yet, so we can't look for the reward @@ -507,7 +513,7 @@ pub fn load_nakamoto_reward_set( sort_db.conn(), &anchor_block_header.consensus_hash, )? - .expect("FATAL: no snapshot for winning PoX anchor block"); + .ok_or_else(|| ChainstateError::Expects("no snapshot for winning PoX anchor block".into()))?; // make sure the `anchor_block` field is the same as whatever goes into the block-commit, // or PoX ancestry queries won't work. @@ -683,15 +689,12 @@ impl< debug!("Received new epoch 2.x Stacks block notice"); match self.handle_new_stacks_block() { Ok(missing_block_opt) => { - if missing_block_opt.is_some() { - debug!( - "Missing affirmed anchor block: {:?}", - &missing_block_opt.as_ref().expect("unreachable") - ); + if let Some(missing_block) = &missing_block_opt { + debug!("Missing affirmed anchor block: {missing_block:?}"); } } Err(e) => { - warn!("Error processing new stacks block: {:?}", e); + warn!("Error processing new stacks block: {e:?}"); } } } @@ -761,9 +764,11 @@ impl< /// DB. pub fn handle_new_nakamoto_stacks_block(&mut self) -> Result, Error> { debug!("Handle new Nakamoto block"); - let canonical_sortition_tip = self.canonical_sortition_tip.clone().expect( - "FAIL: processing a new Stacks block, but don't have a canonical sortition tip", - ); + let canonical_sortition_tip = self.canonical_sortition_tip.clone().ok_or_else(|| { + ChainstateError::Expects( + "processing a new Stacks block, but don't have a canonical sortition tip".into(), + ) + })?; loop { Self::fault_injection_pause_nakamoto_block_processing(); @@ -822,7 +827,11 @@ impl< .header .anchored_header .as_stacks_nakamoto() - .expect("FATAL: unreachable: processed a non-Nakamoto block"); + .ok_or_else(|| { + ChainstateError::Expects( + "unreachable: processed a non-Nakamoto block".into(), + ) + })?; ( nakamoto_header.block_id(), @@ -831,7 +840,7 @@ impl< ) }; - debug!("Bump blocks processed ({})", &canonical_stacks_block_id); + debug!("Bump blocks processed ({canonical_stacks_block_id})"); self.notifier.notify_stacks_block_processed(); increment_stx_blocks_processed_counter(); @@ -850,7 +859,7 @@ impl< self.sortition_db.conn(), &block_receipt.evaluated_epoch, )? - .expect("Could not find a stacks epoch."); + .ok_or_else(|| ChainstateError::Expects("Could not find a stacks epoch.".into()))?; estimator.notify_block( &block_receipt.tx_receipts, &stacks_epoch.block_limit, @@ -864,7 +873,7 @@ impl< self.sortition_db.conn(), &block_receipt.evaluated_epoch, )? - .expect("Could not find a stacks epoch."); + .ok_or_else(|| ChainstateError::Expects("Could not find a stacks epoch.".into()))?; if let Err(e) = estimator.notify_block(&block_receipt, &stacks_epoch.block_limit) { warn!("FeeEstimator failed to process block receipt"; "stacks_block_hash" => %block_hash, @@ -879,12 +888,7 @@ impl< self.sortition_db.conn(), &canonical_stacks_consensus_hash, )? - .unwrap_or_else(|| { - panic!( - "FATAL: unreachable: consensus hash {} has no snapshot", - &canonical_stacks_consensus_hash - ) - }); + .ok_or_else(|| ChainstateError::Expects(format!("FATAL: unreachable: consensus hash {canonical_stacks_consensus_hash} has no snapshot")))?; // are we in the prepare phase? // TODO: this should *not* include the 0 block! @@ -900,9 +904,9 @@ impl< let current_reward_cycle = self .burnchain .block_height_to_reward_cycle(stacks_sn.block_height) - .unwrap_or_else(|| { - panic!("FATAL: unreachable: burnchain block height has no reward cycle") - }); + .ok_or_else(|| { + ChainstateError::Expects(format!("burnchain block height has no reward cycle")) + })?; let last_processed_reward_cycle = { let canonical_sn = SortitionDB::get_block_snapshot( @@ -916,7 +920,9 @@ impl< let Some((rc_info, _)) = load_nakamoto_reward_set( self.burnchain .block_height_to_reward_cycle(canonical_sn.block_height) - .expect("FATAL: snapshot has no reward cycle"), + .ok_or_else(|| { + ChainstateError::Expects("snapshot has no reward cycle".into()) + })?, &canonical_sn.sortition_id, &self.burnchain, &mut self.chain_state_db, @@ -954,10 +960,9 @@ impl< stacks_tip: &StacksBlockId, reward_cycle: u64, ) -> Result, Error> { - let sortition_tip_id = self - .canonical_sortition_tip - .as_ref() - .expect("FATAL: Processing anchor block, but no known sortition tip"); + let sortition_tip_id = self.canonical_sortition_tip.as_ref().ok_or_else(|| { + ChainstateError::Expects("Processing anchor block, but no known sortition tip".into()) + })?; get_nakamoto_reward_cycle_info( sortition_tip_id, diff --git a/stackslib/src/chainstate/nakamoto/signer_set.rs b/stackslib/src/chainstate/nakamoto/signer_set.rs index eb0eb46bce..eb240995db 100644 --- a/stackslib/src/chainstate/nakamoto/signer_set.rs +++ b/stackslib/src/chainstate/nakamoto/signer_set.rs @@ -54,31 +54,45 @@ impl RawRewardSetEntry { pub fn from_pox_4_tuple(is_mainnet: bool, tuple: TupleData) -> Result { let mut tuple_data = tuple.data_map; - let pox_addr_tuple = tuple_data - .remove("pox-addr") - .expect("FATAL: no `pox-addr` in return value from (get-reward-set-pox-address)"); + let pox_addr_tuple = tuple_data.remove("pox-addr").ok_or_else(|| { + ChainstateError::Expects( + "no `pox-addr` in return value from (get-reward-set-pox-address)".into(), + ) + })?; let reward_address = PoxAddress::try_from_pox_tuple(is_mainnet, &pox_addr_tuple) - .unwrap_or_else(|| panic!("FATAL: not a valid PoX address: {pox_addr_tuple}")); + .ok_or_else(|| { + ChainstateError::Expects(format!("not a valid PoX address: {pox_addr_tuple}")) + })?; let total_ustx = tuple_data .remove("total-ustx") - .expect( - "FATAL: no 'total-ustx' in return value from (pox-4.get-reward-set-pox-address)", - ) - .expect_u128() - .expect("FATAL: total-ustx is not a u128"); + .ok_or_else(|| { + ChainstateError::Expects( + "no 'total-ustx' in return value from (pox-4.get-reward-set-pox-address)" + .into(), + ) + })? + .expect_u128()?; let stacker = tuple_data .remove("stacker") - .expect("FATAL: no 'stacker' in return value from (pox-4.get-reward-set-pox-address)") + .ok_or_else(|| { + ChainstateError::Expects( + "no 'stacker' in return value from (pox-4.get-reward-set-pox-address)".into(), + ) + })? .expect_optional()? .map(|value| value.expect_principal()) .transpose()?; let signer = tuple_data .remove("signer") - .expect("FATAL: no 'signer' in return value from (pox-4.get-reward-set-pox-address)") + .ok_or_else(|| { + ChainstateError::Expects( + "no 'signer' in return value from (pox-4.get-reward-set-pox-address)".into(), + ) + })? .expect_buff(SIGNERS_PK_LEN)?; // (buff 33) only enforces max size, not min size, so we need to do a len check @@ -145,12 +159,9 @@ impl NakamotoSigners { ], )? .expect_optional()? - .unwrap_or_else(|| { - panic!( - "FATAL: missing PoX address in slot {} out of {} in reward cycle {}", - index, list_length, reward_cycle - ) - }) + .ok_or_else(|| { + ChainstateError::Expects(format!("Missing PoX address in slot {index} out of {list_length} in reward cycle {reward_cycle}")) + })? .expect_tuple()?; let entry = RawRewardSetEntry::from_pox_4_tuple(is_mainnet, tuple)?; @@ -196,20 +207,16 @@ impl NakamotoSigners { .map(|signer| { let signer_hash = Hash160::from_data(&signer.signing_key); let signing_address = StacksAddress::p2pkh_from_hash(is_mainnet, signer_hash); - Value::Tuple( - TupleData::from_data(vec![ - ( - "signer".into(), - Value::Principal(PrincipalData::from(signing_address)), - ), - ("num-slots".into(), Value::UInt(1)) - ]) - .expect( - "BUG: Failed to construct `{ signer: principal, num-slots: u64 }` tuple", - ), - ) + let tuple_data = TupleData::from_data(vec![ + ( + "signer".into(), + Value::Principal(PrincipalData::from(signing_address)), + ), + ("num-slots".into(), Value::UInt(1)), + ])?; + Ok::(Value::Tuple(tuple_data)) }) - .collect() + .collect::, _>>()? }; let signers_list = if participation == 0 { @@ -223,66 +230,55 @@ impl NakamotoSigners { .map(|signer| { let signer_hash = Hash160::from_data(&signer.signing_key); let signing_address = StacksAddress::p2pkh_from_hash(is_mainnet, signer_hash); - Value::Tuple( - TupleData::from_data(vec![ - ( - "signer".into(), - Value::Principal(PrincipalData::from(signing_address)), - ), - ("weight".into(), Value::UInt(signer.weight.into())), - ]) - .expect( - "BUG: Failed to construct `{ signer: principal, weight: uint }` tuple", + let tuple = TupleData::from_data(vec![ + ( + "signer".into(), + Value::Principal(PrincipalData::from(signing_address)), ), - ) + ("weight".into(), Value::UInt(signer.weight.into())), + ])?; + Ok::(Value::Tuple(tuple)) }) - .collect() + .collect::, _>>()? }; if signers_list.len() > SIGNERS_MAX_LIST_SIZE { - panic!( - "FATAL: signers list returned by reward set calculations longer than maximum ({} > {})", - signers_list.len(), - SIGNERS_MAX_LIST_SIZE, - ); + return Err(ChainstateError::Expects(format!( + "signers list returned by reward set calculations longer than maximum ({} > {SIGNERS_MAX_LIST_SIZE})", + signers_list.len() + ))); } let set_stackerdb_args = [ - SymbolicExpression::atom_value(Value::cons_list_unsanitized(stackerdb_list).expect( - "BUG: Failed to construct `(list 4000 { signer: principal, num-slots: u64 })` list", - )), + SymbolicExpression::atom_value(Value::cons_list_unsanitized(stackerdb_list)?), SymbolicExpression::atom_value(Value::UInt(reward_cycle.into())), SymbolicExpression::atom_value(Value::UInt(coinbase_height.into())), ]; let set_signers_args = [ SymbolicExpression::atom_value(Value::UInt(reward_cycle.into())), - SymbolicExpression::atom_value(Value::cons_list_unsanitized(signers_list).expect( - "BUG: Failed to construct `(list 4000 { signer: principal, weight: uint })` list", - )), + SymbolicExpression::atom_value(Value::cons_list_unsanitized(signers_list)?), ]; - let (value, _, events, _) = clarity - .with_abort_callback( - |vm_env| { - vm_env.execute_in_env(sender_addr.clone(), None, None, |env| { - env.execute_contract_allow_private( - signers_contract, - "stackerdb-set-signer-slots", - &set_stackerdb_args, - false, - )?; - env.execute_contract_allow_private( - signers_contract, - "set-signers", - &set_signers_args, - false, - ) - }) - }, - |_, _| None, - ) - .expect("FATAL: failed to update signer stackerdb"); + let (value, _, events, _) = clarity.with_abort_callback( + |vm_env| { + vm_env.execute_in_env(sender_addr.clone(), None, None, |env| { + env.execute_contract_allow_private( + signers_contract, + "stackerdb-set-signer-slots", + &set_stackerdb_args, + false, + )?; + env.execute_contract_allow_private( + signers_contract, + "set-signers", + &set_signers_args, + false, + ) + }) + }, + |_, _| None, + )?; if let Value::Response(ref data) = value { if !data.committed { @@ -291,7 +287,9 @@ impl NakamotoSigners { "reward_cycle" => reward_cycle, "cc_response" => %value, ); - panic!(); + return Err(ChainstateError::Expects( + "Failed to update .signers contract".into(), + )); } } @@ -343,24 +341,23 @@ impl NakamotoSigners { let signers_contract = &boot_code_id(SIGNERS_NAME, clarity_tx.config.mainnet); // are we the first block in the prepare phase in our fork? - let needs_update: Result<_, ChainstateError> = clarity_tx.connection().with_clarity_db_readonly(|clarity_db| { - if !clarity_db.has_contract(signers_contract) { - // if there's no signers contract, no need to update anything. - return Ok(false) - } - let Ok(value) = clarity_db.lookup_variable_unknown_descriptor( - signers_contract, - SIGNERS_UPDATE_STATE, - ¤t_epoch, - ) else { - error!("FATAL: Failed to read `{SIGNERS_UPDATE_STATE}` variable from .signers contract"); - panic!(); - }; - let cycle_number = value.expect_u128()?; - // if the cycle_number is less than `cycle_of_prepare_phase`, we need to update - // the .signers state. - Ok(cycle_number < u128::from(cycle_of_prepare_phase)) - }); + let needs_update: Result<_, ChainstateError> = clarity_tx + .connection() + .with_clarity_db_readonly(|clarity_db| { + if !clarity_db.has_contract(signers_contract) { + // if there's no signers contract, no need to update anything. + return Ok(false); + } + let value = clarity_db.lookup_variable_unknown_descriptor( + signers_contract, + SIGNERS_UPDATE_STATE, + ¤t_epoch, + )?; + let cycle_number = value.expect_u128()?; + // if the cycle_number is less than `cycle_of_prepare_phase`, we need to update + // the .signers state. + Ok(cycle_number < u128::from(cycle_of_prepare_phase)) + }); if !needs_update? { debug!("Current cycle has already been setup in .signers or .signers is not initialized yet"); @@ -416,7 +413,7 @@ impl NakamotoSigners { sortdb, block_id, SIGNERS_NAME, - &format!("(get-signers u{})", reward_cycle), + &format!("(get-signers u{reward_cycle})"), )? .expect_optional()?; let mut signers = HashMap::new(); @@ -427,13 +424,12 @@ impl NakamotoSigners { let signer_address = if let PrincipalData::Standard(signer) = principal_data { signer.into() } else { - panic!( - "FATAL: Signer returned from get-signers is not a standard principal: {:?}", - principal_data - ); + return Err(ChainstateError::Expects(format!("Signer returned from get-signers is not a standard principal: {principal_data:?}"))); }; let weight = u64::try_from(signer_tuple.get("weight")?.to_owned().expect_u128()?) - .expect("FATAL: Signer weight greater than a u64::MAX"); + .map_err(|_| { + ChainstateError::Expects("Signer weight greater than a u64::MAX".into()) + })?; signers.insert(signer_address, weight); } } diff --git a/stackslib/src/chainstate/stacks/boot/mod.rs b/stackslib/src/chainstate/stacks/boot/mod.rs index 7464c49052..8353e4a076 100644 --- a/stackslib/src/chainstate/stacks/boot/mod.rs +++ b/stackslib/src/chainstate/stacks/boot/mod.rs @@ -1267,13 +1267,12 @@ impl StacksChainState { sortdb, block_id, POX_4_NAME, - &format!("(get-reward-set-size u{})", reward_cycle), + &format!("(get-reward-set-size u{reward_cycle})"), )? .expect_u128()?; debug!( - "At block {:?} (reward cycle {}): {} PoX reward addresses", - block_id, reward_cycle, num_addrs + "At block {block_id:?} (reward cycle {reward_cycle}): {num_addrs} PoX reward addresses" ); let mut ret = vec![]; @@ -1290,13 +1289,12 @@ impl StacksChainState { sortdb, block_id, POX_4_NAME, - &format!("(get-reward-set-pox-address u{} u{})", reward_cycle, i), + &format!("(get-reward-set-pox-address u{reward_cycle} u{i})"), )? .expect_optional()? .unwrap_or_else(|| { panic!( - "FATAL: missing PoX address in slot {} out of {} in reward cycle {}", - i, num_addrs, reward_cycle + "FATAL: missing PoX address in slot {i} out of {num_addrs} in reward cycle {reward_cycle}" ) }) .expect_tuple()?; @@ -1380,12 +1378,11 @@ impl StacksChainState { sortdb, block_id, SIGNERS_VOTING_NAME, - &format!("(get-approved-aggregate-key u{})", reward_cycle), + &format!("(get-approved-aggregate-key u{reward_cycle})"), )? .expect_optional()?; debug!( - "Aggregate public key for reward cycle {} is {:?}", - reward_cycle, aggregate_public_key_opt + "Aggregate public key for reward cycle {reward_cycle} is {aggregate_public_key_opt:?}" ); let aggregate_public_key = match aggregate_public_key_opt { diff --git a/stackslib/src/chainstate/stacks/db/blocks.rs b/stackslib/src/chainstate/stacks/db/blocks.rs index 509acac4f7..47bf58e51d 100644 --- a/stackslib/src/chainstate/stacks/db/blocks.rs +++ b/stackslib/src/chainstate/stacks/db/blocks.rs @@ -4593,55 +4593,45 @@ impl StacksChainState { ) -> Result<(u128, Vec), Error> { let mainnet = clarity_tx.config.mainnet; let lockup_contract_id = boot_code_id("lockup", mainnet); - clarity_tx - .connection() - .as_transaction(|tx_connection| { - let epoch = tx_connection.get_epoch(); - let result = tx_connection.with_clarity_db(|db| { - let block_height = Value::UInt(db.get_current_block_height().into()); - let res = db.fetch_entry_unknown_descriptor( - &lockup_contract_id, - "lockups", - &block_height, - &epoch, - )?; - Ok(res) - })?; + clarity_tx.connection().as_transaction(|tx_connection| { + let epoch = tx_connection.get_epoch(); + let result = tx_connection.with_clarity_db(|db| { + let block_height = Value::UInt(db.get_current_block_height().into()); + let res = db.fetch_entry_unknown_descriptor( + &lockup_contract_id, + "lockups", + &block_height, + &epoch, + )?; + Ok(res) + })?; - let entries = match result { - Value::Optional(_) => match result.expect_optional()? { - Some(Value::Sequence(SequenceData::List(entries))) => entries.data, - _ => return Ok((0, vec![])), - }, + let entries = match result { + Value::Optional(_) => match result.expect_optional()? { + Some(Value::Sequence(SequenceData::List(entries))) => entries.data, _ => return Ok((0, vec![])), - }; + }, + _ => return Ok((0, vec![])), + }; - let mut total_minted = 0; - let mut events = vec![]; - for entry in entries.into_iter() { - let schedule: TupleData = entry.expect_tuple()?; - let amount = schedule - .get("amount") - .expect("Lockup malformed") - .to_owned() - .expect_u128()?; - let recipient = schedule - .get("recipient") - .expect("Lockup malformed") - .to_owned() - .expect_principal()?; - total_minted += amount; - StacksChainState::account_credit( - tx_connection, - &recipient, - u64::try_from(amount).expect("FATAL: transferred more STX than exist"), - ); - let event = STXEventType::STXMintEvent(STXMintEventData { recipient, amount }); - events.push(StacksTransactionEvent::STXEvent(event)); - } - Ok((total_minted, events)) - }) - .map_err(Error::ClarityError) + let mut total_minted = 0; + let mut events = vec![]; + for entry in entries.into_iter() { + let schedule: TupleData = entry.expect_tuple()?; + let amount = schedule.get("amount")?.to_owned().expect_u128()?; + let recipient = schedule.get("recipient")?.to_owned().expect_principal()?; + total_minted += amount; + StacksChainState::account_credit( + tx_connection, + &recipient, + u64::try_from(amount) + .map_err(|_| Error::Expects("transferred more STX than exists".into()))?, + ); + let event = STXEventType::STXMintEvent(STXMintEventData { recipient, amount }); + events.push(StacksTransactionEvent::STXEvent(event)); + } + Ok((total_minted, events)) + }) } /// Given the list of matured miners, find the miner reward schedule that produced the parent diff --git a/stackslib/src/chainstate/stacks/db/transactions.rs b/stackslib/src/chainstate/stacks/db/transactions.rs index fb587c6876..d85bbd9871 100644 --- a/stackslib/src/chainstate/stacks/db/transactions.rs +++ b/stackslib/src/chainstate/stacks/db/transactions.rs @@ -956,12 +956,8 @@ impl StacksChainState { .get_microblock_poison_report(mblock_pubk_height)? { // account for report loaded - env.add_memory(u64::from( - TypeSignature::PrincipalType - .size() - .map_err(VmExecutionError::from)?, - )) - .map_err(|e| Error::from_cost_error(e, cost_before.clone(), env.global_context))?; + env.add_memory(u64::from(TypeSignature::PrincipalType.size()?)) + .map_err(|e| Error::from_cost_error(e, cost_before.clone(), env.global_context))?; // u128 sequence env.add_memory(16) diff --git a/stackslib/src/chainstate/stacks/mod.rs b/stackslib/src/chainstate/stacks/mod.rs index 1d86ab3c26..3a19e8df0f 100644 --- a/stackslib/src/chainstate/stacks/mod.rs +++ b/stackslib/src/chainstate/stacks/mod.rs @@ -20,7 +20,7 @@ use std::{error, fmt, io}; use clarity::vm::contexts::GlobalContext; use clarity::vm::costs::{CostErrors, ExecutionCost}; -use clarity::vm::errors::VmExecutionError; +use clarity::vm::errors::{ClarityTypeError, StaticCheckError, VmExecutionError}; use clarity::vm::representations::{ClarityName, ContractName}; use clarity::vm::types::{ PrincipalData, QualifiedContractIdentifier, StandardPrincipalData, Value, @@ -122,6 +122,8 @@ pub enum Error { InvalidChildOfNakomotoBlock, NoRegisteredSigners(u64), TenureTooBigError, + /// This error indicates an internal state or condition that should never actually happen + Expects(String), } impl From for Error { @@ -225,6 +227,7 @@ impl fmt::Display for Error { write!(f, "The supplied block identifiers are not in the same fork") } Error::TenureTooBigError => write!(f, "Too much data in tenure"), + Error::Expects(ref msg) => write!(f, "Unexpected state: {msg}"), } } } @@ -272,6 +275,7 @@ impl error::Error for Error { Error::NoRegisteredSigners(_) => None, Error::NotInSameFork => None, Error::TenureTooBigError => None, + Error::Expects(ref _msg) => None, } } } @@ -319,6 +323,7 @@ impl Error { Error::NoRegisteredSigners(_) => "NoRegisteredSigners", Error::NotInSameFork => "NotInSameFork", Error::TenureTooBigError => "TenureTooBigError", + Error::Expects(_) => "Expects", } } @@ -353,6 +358,15 @@ impl From for Error { } } +/// TODO: remove this comment. Should this actually convert to a static check +/// or is it possible for this to be a runtime error...I don't think so because +/// if its a runtime issue, it would be really hitting VmExecutionError already +impl From for Error { + fn from(e: ClarityTypeError) -> Error { + Error::ClarityError(ClarityError::StaticCheck(StaticCheckError::from(e))) + } +} + impl Error { pub fn from_cost_error( err: CostErrors, diff --git a/stackslib/src/chainstate/tests/runtime_analysis_tests.rs b/stackslib/src/chainstate/tests/runtime_analysis_tests.rs index 22a688127c..3238dbdaba 100644 --- a/stackslib/src/chainstate/tests/runtime_analysis_tests.rs +++ b/stackslib/src/chainstate/tests/runtime_analysis_tests.rs @@ -91,7 +91,8 @@ fn variant_coverage_report(variant: CheckErrorKind) { "least_supertype checks already run in analysis, and runtime values are sanitized to their declared signatures, so the VM never sees a pair of values whose unified type wasn't accepted earlier."), - Expects(_) => Unreachable_ExpectLike, + ExpectsAcceptable(_) => Unreachable_ExpectLike, + ExpectsRejectable(_) => Unreachable_ExpectLike, BadMatchOptionSyntax(_) => Unreachable_Functionally( "Both the analyzer and the runtime examine the exact same match AST slice. The static pass invokes check_special_match_opt, which enforces the 3 diff --git a/stackslib/src/chainstate/tests/runtime_tests.rs b/stackslib/src/chainstate/tests/runtime_tests.rs index b11db2a6cf..532d012a4d 100644 --- a/stackslib/src/chainstate/tests/runtime_tests.rs +++ b/stackslib/src/chainstate/tests/runtime_tests.rs @@ -127,11 +127,6 @@ fn variant_coverage_report(variant: RuntimeError) { are significantly lower and will trigger first. Only low-level Rust unit tests \ can construct a context deep enough to hit this error." ), - BadTypeConstruction => Unreachable_Functionally( - "BadTypeConstruction is rejected during static analysis at contract-publish time. \ - Any value construction that would produce an ill-formed type fails parsing or \ - type-checking before the contract is stored on-chain." - ), BadBlockHeight(_) => Unreachable_Functionally( "All block heights referenced via `at-block` or `get-block-info?` are guaranteed \ to exist in the node's historical database during normal execution. \ @@ -154,11 +149,6 @@ fn variant_coverage_report(variant: RuntimeError) { "Every on-chain transaction and contract-call has a well-defined sender. \ This error only occurs in malformed test harnesses." ), - BadNameValue(_, _) => Unreachable_Functionally( - "Contract, function, trait, and variable names are fully validated during static analysis at publish time. \ - The runtime only ever encounters already-validated names. \ - Only corrupted state or manual VM manipulation can produce this error." - ), UnknownBlockHeaderHash(_) => Tested(vec![unknown_block_header_hash_fork]), BadBlockHash(_) => Tested(vec![bad_block_hash]), UnwrapFailure => Tested(vec![ diff --git a/stackslib/src/chainstate/tests/static_analysis_tests.rs b/stackslib/src/chainstate/tests/static_analysis_tests.rs index 1b918fd294..4f402174a8 100644 --- a/stackslib/src/chainstate/tests/static_analysis_tests.rs +++ b/stackslib/src/chainstate/tests/static_analysis_tests.rs @@ -68,7 +68,8 @@ fn variant_coverage_report(variant: StaticCheckErrorKind) { TypeSignatureTooDeep => Tested(vec![static_check_error_type_signature_too_deep]), ExpectedName => Tested(vec![static_check_error_expected_name]), SupertypeTooLarge => Tested(vec![static_check_error_supertype_too_large]), - Expects(_) => Unreachable_ExpectLike, + ExpectsAcceptable(_) => Unreachable_ExpectLike, + ExpectsRejectable(_) => Unreachable_ExpectLike, BadMatchOptionSyntax(static_check_error_kind) => { Tested(vec![static_check_error_bad_match_option_syntax]) } diff --git a/stackslib/src/net/api/getpoxinfo.rs b/stackslib/src/net/api/getpoxinfo.rs index f00a3008f6..76ee16f87c 100644 --- a/stackslib/src/net/api/getpoxinfo.rs +++ b/stackslib/src/net/api/getpoxinfo.rs @@ -206,34 +206,39 @@ impl RPCPoxInfoData { }; let first_burnchain_block_height = res - .get("first-burnchain-block-height") - .unwrap_or_else(|_| panic!("FATAL: no 'first-burnchain-block-height'")) + .get("first-burnchain-block-height")? .to_owned() - .expect_u128()? as u64; + .expect_u128()? + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; let min_stacking_increment_ustx = res - .get("min-amount-ustx") - .unwrap_or_else(|_| panic!("FATAL: no 'min-amount-ustx'")) + .get("min-amount-ustx")? .to_owned() - .expect_u128()? as u64; + .expect_u128()? + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; let prepare_cycle_length = res - .get("prepare-cycle-length") - .unwrap_or_else(|_| panic!("FATAL: no 'prepare-cycle-length'")) + .get("prepare-cycle-length")? .to_owned() - .expect_u128()? as u64; + .expect_u128()? + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; let reward_cycle_length = res - .get("reward-cycle-length") - .unwrap_or_else(|_| panic!("FATAL: no 'reward-cycle-length'")) + .get("reward-cycle-length")? .to_owned() - .expect_u128()? as u64; + .expect_u128()? + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; let total_liquid_supply_ustx = res - .get("total-liquid-supply-ustx") - .unwrap_or_else(|_| panic!("FATAL: no 'total-liquid-supply-ustx'")) + .get("total-liquid-supply-ustx")? .to_owned() - .expect_u128()? as u64; + .expect_u128()? + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; let has_rejection_data = pox_contract_name == POX_1_NAME || pox_contract_name == POX_2_NAME @@ -241,21 +246,24 @@ impl RPCPoxInfoData { let (rejection_fraction, rejection_votes_left_required) = if has_rejection_data { let rejection_fraction = res - .get("rejection-fraction") - .unwrap_or_else(|_| panic!("FATAL: no 'rejection-fraction'")) + .get("rejection-fraction")? .to_owned() - .expect_u128()? as u64; + .expect_u128()? + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; let current_rejection_votes = res - .get("current-rejection-votes") - .unwrap_or_else(|_| panic!("FATAL: no 'current-rejection-votes'")) + .get("current-rejection-votes")? .to_owned() - .expect_u128()? as u64; + .expect_u128()? + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; - let total_required = (total_liquid_supply_ustx as u128 / 100) + let total_required: u64 = (total_liquid_supply_ustx as u128 / 100) .checked_mul(rejection_fraction as u128) .ok_or_else(|| NetError::DBError(DBError::Overflow))? - as u64; + .try_into() + .map_err(|_| NetError::DBError(DBError::Overflow))?; let votes_left = total_required.saturating_sub(current_rejection_votes); (Some(rejection_fraction), Some(votes_left)) diff --git a/stackslib/src/net/mod.rs b/stackslib/src/net/mod.rs index 55e9e5de95..ef366f95f9 100644 --- a/stackslib/src/net/mod.rs +++ b/stackslib/src/net/mod.rs @@ -19,7 +19,7 @@ use std::io::{Read, Write}; use std::net::{IpAddr, SocketAddr}; use std::{error, fmt, io}; -use clarity::vm::errors::VmExecutionError; +use clarity::vm::errors::{ClarityTypeError, StaticCheckError, VmExecutionError}; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use libstackerdb::{Error as libstackerdb_error, StackerDBChunkData}; use p2p::{DropReason, DropSource}; @@ -533,6 +533,15 @@ impl From for Error { } } +/// TODO: remove this comment. Should this actually convert to a static check +/// or is it possible for this to be a runtime error...I don't think so because +/// if its a runtime issue, it would be really hitting VmExecutionError already +impl From for Error { + fn from(e: ClarityTypeError) -> Self { + Error::ClarityError(ClarityError::StaticCheck(StaticCheckError::from(e))) + } +} + impl From for Error { fn from(e: VmExecutionError) -> Self { Error::ClarityError(e.into()) diff --git a/stackslib/src/util_lib/strings.rs b/stackslib/src/util_lib/strings.rs index 86592ab5e3..e990df70c7 100644 --- a/stackslib/src/util_lib/strings.rs +++ b/stackslib/src/util_lib/strings.rs @@ -19,7 +19,7 @@ use std::fmt; use std::io::{Read, Write}; use std::ops::{Deref, DerefMut}; -use clarity::vm::errors::RuntimeError; +use clarity::vm::errors::ClarityTypeError; use clarity::vm::representations::{ ClarityName, ContractName, MAX_STRING_LEN as CLARITY_MAX_STRING_LENGTH, }; @@ -38,11 +38,10 @@ lazy_static! { guarded_string!( UrlString, - "UrlString", URL_STRING_REGEX, CLARITY_MAX_STRING_LENGTH, - RuntimeError, - RuntimeError::BadNameValue + ClarityTypeError, + ClarityTypeError::InvalidUrlString ); /// printable-ASCII-only string, but encodable.