Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 59 additions & 15 deletions clarity-types/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ use crate::types::{FunctionIdentifier, Value};

pub type StackTrace = Vec<FunctionIdentifier>;

/// Wraps error types that do not implement [`PartialEq`], enabling their
/// use in enums that implement the trait. Any two `IncomparableError` values
/// are always considered unequal.
#[derive(Debug)]
pub struct IncomparableError<T> {
/// The wrapped error value.
pub err: T,
}

Expand All @@ -44,28 +48,68 @@ pub enum Error {
/// TypeChecker and other check passes. Test executions may
/// trigger these errors.
Unchecked(CheckErrorKind),
Interpreter(InterpreterError),
/// A critical, unrecoverable bug within the VM's internal logic.
///
/// The presence of this error indicates a violation of one of the VM's
/// invariants or a corrupted state. This is **not** an error in the user's
/// Clarity code, but a bug in the VM's Rust implementation.
///
/// # Example
/// The VM's evaluation loop attempts to `pop` from an empty internal call stack,
/// indicating a mismatch in function entry/exit logic.
Internal(VmInternalError),
Runtime(RuntimeErrorType, Option<StackTrace>),
EarlyReturn(EarlyReturnError),
}

/// InterpreterErrors are errors that *should never* occur.
/// Test executions may trigger these errors.
/// Represents an internal, unrecoverable error within the Clarity VM.
///
/// These errors signify a bug in the VM's logic or a violation of its internal
/// invariants. They are not meant to be caught or handled by Clarity contracts.
#[derive(Debug, PartialEq)]
pub enum InterpreterError {
pub enum VmInternalError {
/// Raised when the VM encounters an invalid or malformed `SymbolicExpression`
/// e.g., bad variable name or missing argument.
/// The `String` provides a message describing the specific issue.
BadSymbolicRepresentation(String),
InterpreterError(String),
/// A generic, unexpected internal error, indicating a logic failure within
/// the VM.
/// The `String` provides a message describing the specific failure.
InvariantViolation(String), // TODO: merge with VmInternalError::Expect
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue for this TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point! I just opened it #6530

/// The VM failed to produce the final `AssetMap` when finalizing the
/// execution environment for a transaction.
FailedToConstructAssetTable,
/// The VM failed to produce the final `EventBatch` when finalizing the
/// execution environment for a transaction.
FailedToConstructEventBatch,
/// An error occurred during an interaction with the database.
/// The parameter contains the corresponding SQLite error.
#[cfg(feature = "rusqlite")]
SqliteError(IncomparableError<SqliteError>),
/// The file path provided for the MARF database is invalid because it
/// contains non-UTF-8 characters.
BadFileName,
/// The VM failed to create the necessary directory for the MARF persistent
/// storage. Likely due to a file system permissions error or an invalid path
FailedToCreateDataDirectory,
/// A failure occurred within the MARF implementation.
/// The `String` provides a message describing the specific failure.
MarfFailure(String),
/// Failed to construct a tuple value from provided data because it did not
/// match the expected type signature.
FailureConstructingTupleWithType,
/// Failed to construct a list value from provided data because it
/// did not match the expected type signature.
FailureConstructingListWithType,
/// An STX transfer failed due to insufficient balance.
InsufficientBalance,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this truly an unexpected error? Does it imply that the calling code should sanitize inputs first, and that this error should never actually be raised?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be used as “the last stand.”.

Looking at STXBalanceSnapshot::transfer_to(...) (invoked in Environment::stx_transfer_consolidated(..)), it attempts to check the amount immediately before performing the transfer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly -- it's the final check to prevent balance underflows. Balance underflows are checked at several places up the stack, so if this error is needed then Bad Things have already happened in the VM.

/// A generic error occurred during a database operation.
/// The `String` represents a descriptive message detailing the specific issue.
DBError(String),
/// An internal expectation or assertion failed. This is used for conditions
/// that are believed to be unreachable but are handled gracefully to prevent
/// a panic.
/// The `String` provides a message describing the failed expectation.
Expect(String),
}

Expand Down Expand Up @@ -130,7 +174,7 @@ impl PartialEq<Error> for Error {
(Error::Runtime(x, _), Error::Runtime(y, _)) => x == y,
(Error::Unchecked(x), Error::Unchecked(y)) => x == y,
(Error::EarlyReturn(x), Error::EarlyReturn(y)) => x == y,
(Error::Interpreter(x), Error::Interpreter(y)) => x == y,
(Error::Internal(x), Error::Internal(y)) => x == y,
_ => false,
}
}
Expand Down Expand Up @@ -175,7 +219,7 @@ impl error::Error for RuntimeErrorType {
impl From<ParseError> for Error {
fn from(err: ParseError) -> Self {
match *err.err {
ParseErrors::InterpreterFailure => Error::from(InterpreterError::Expect(
ParseErrors::InterpreterFailure => Error::from(VmInternalError::Expect(
"Unexpected interpreter failure during parsing".into(),
)),
_ => Error::from(RuntimeErrorType::ASTError(Box::new(err))),
Expand All @@ -186,10 +230,10 @@ impl From<ParseError> for Error {
impl From<CostErrors> for Error {
fn from(err: CostErrors) -> Self {
match err {
CostErrors::InterpreterFailure => Error::from(InterpreterError::Expect(
CostErrors::InterpreterFailure => Error::from(VmInternalError::Expect(
"Interpreter failure during cost calculation".into(),
)),
CostErrors::Expect(s) => Error::from(InterpreterError::Expect(format!(
CostErrors::Expect(s) => Error::from(VmInternalError::Expect(format!(
"Interpreter failure during cost calculation: {s}"
))),
other_err => Error::from(CheckErrorKind::from(other_err)),
Expand Down Expand Up @@ -221,9 +265,9 @@ impl From<EarlyReturnError> for Error {
}
}

impl From<InterpreterError> for Error {
fn from(err: InterpreterError) -> Self {
Error::Interpreter(err)
impl From<VmInternalError> for Error {
fn from(err: VmInternalError) -> Self {
Error::Internal(err)
}
}

Expand Down Expand Up @@ -252,12 +296,12 @@ mod test {
Error::EarlyReturn(EarlyReturnError::UnwrapFailed(Box::new(Value::Bool(true))))
);
assert_eq!(
Error::Interpreter(InterpreterError::InterpreterError("".to_string())),
Error::Interpreter(InterpreterError::InterpreterError("".to_string()))
Error::Internal(VmInternalError::InvariantViolation("".to_string())),
Error::Internal(VmInternalError::InvariantViolation("".to_string()))
);
assert!(
Error::EarlyReturn(EarlyReturnError::UnwrapFailed(Box::new(Value::Bool(true))))
!= Error::Interpreter(InterpreterError::InterpreterError("".to_string()))
!= Error::Internal(VmInternalError::InvariantViolation("".to_string()))
);
}
}
Loading
Loading