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
59 changes: 45 additions & 14 deletions clarity-types/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub enum Error {
/// trigger these errors.
Unchecked(CheckErrorKind),
Interpreter(InterpreterError),
Runtime(RuntimeErrorType, Option<StackTrace>),
Runtime(RuntimeError, Option<StackTrace>),
EarlyReturn(EarlyReturnError),
}

Expand All @@ -69,37 +69,68 @@ pub enum InterpreterError {
Expect(String),
}

/// RuntimeErrors are errors that smart contracts are expected
/// to be able to trigger during execution (e.g., arithmetic errors)
/// Runtime errors that Clarity smart contracts are expected to trigger during execution in the virtual
/// machine, such as arithmetic errors, invalid operations, or blockchain-specific issues. These errors
/// are distinct from static analysis errors and occur during dynamic evaluation of contract code.
#[derive(Debug, PartialEq)]
pub enum RuntimeErrorType {
pub enum RuntimeError {
/// A generic arithmetic error encountered during contract execution.
/// The `String` represents a descriptive message detailing the specific arithmetic issue.
Arithmetic(String),
/// An arithmetic operation exceeded the maximum value for the data type (e.g., `u128`).
ArithmeticOverflow,
/// An arithmetic operation resulted in a value below zero for an unsigned type.
ArithmeticUnderflow,
/// Attempt to increase token supply beyond the maximum limit.
/// The first u128 represents the attempted new supply (current supply plus increase),
/// and the second represents the maximum allowed supply.
SupplyOverflow(u128, u128),
/// Attempt to decrease token supply below zero.
/// The first `u128` represents the current token supply, and the second represents the attempted decrease amount.
SupplyUnderflow(u128, u128),
/// Attempt to divide or compute modulo by zero.
DivisionByZero,
// error in parsing types
ParseError(String),
// error in parsing the AST
/// Failure to parse types dynamically during contract execution.
/// The `String` represents the specific parsing issue, such as invalid data formats.
TypeParseFailure(String),
/// Failure to parse the abstract syntax tree (AST) during dynamic evaluation.
/// The `Box<ParseError>` wraps the specific parsing error encountered, detailing code interpretation issues.
ASTError(Box<ParseError>),
/// The call stack exceeded the virtual machine's maximum depth.
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),
/// Attempt to interact with a non-existent token (e.g., in NFT or fungible token operations).
NoSuchToken,
/// Feature or function not yet implemented in the virtual machine.
NotImplemented,
/// No caller principal available in the current execution context.
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),
/// Invalid block hash provided (e.g., incorrect format or length).
/// The `Vec<u8>` represents the invalid block hash data.
BadBlockHash(Vec<u8>),
/// Failed to unwrap an `Optional` (`none`) or `Response` (`err` or `ok`) Clarity value.
UnwrapFailure,
/// Attempt to set metadata (e.g., for NFTs or tokens) that was already initialized.
MetadataAlreadySet,
// pox-locking errors
/// Interaction with a deprecated or inactive Proof of Transfer (PoX) contract.
DefunctPoxContract,
/// Attempt to lock STX for stacking when already locked in an active PoX cycle.
PoxAlreadyLocked,

/// Block time unavailable during execution.
BlockTimeNotAvailable,
}

Expand Down Expand Up @@ -154,7 +185,7 @@ impl fmt::Display for Error {
}
}

impl fmt::Display for RuntimeErrorType {
impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
Expand All @@ -166,7 +197,7 @@ impl error::Error for Error {
}
}

impl error::Error for RuntimeErrorType {
impl error::Error for RuntimeError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
Expand All @@ -178,7 +209,7 @@ impl From<ParseError> for Error {
ParseErrors::InterpreterFailure => Error::from(InterpreterError::Expect(
"Unexpected interpreter failure during parsing".into(),
)),
_ => Error::from(RuntimeErrorType::ASTError(Box::new(err))),
_ => Error::from(RuntimeError::ASTError(Box::new(err))),
}
}
}
Expand All @@ -197,8 +228,8 @@ impl From<CostErrors> for Error {
}
}

impl From<RuntimeErrorType> for Error {
fn from(err: RuntimeErrorType) -> Self {
impl From<RuntimeError> for Error {
fn from(err: RuntimeError) -> Self {
Error::Runtime(err, None)
}
}
Expand Down
10 changes: 5 additions & 5 deletions clarity-types/src/representations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use regex::Regex;
use stacks_common::codec::{Error as codec_error, StacksMessageCodec, read_next, write_next};

use crate::Value;
use crate::errors::RuntimeErrorType;
use crate::errors::RuntimeError;
use crate::types::TraitIdentifier;

pub const CONTRACT_MIN_NAME_LENGTH: usize = 1;
Expand Down Expand Up @@ -66,17 +66,17 @@ guarded_string!(
"ClarityName",
CLARITY_NAME_REGEX,
MAX_STRING_LEN,
RuntimeErrorType,
RuntimeErrorType::BadNameValue
RuntimeError,
RuntimeError::BadNameValue
);

guarded_string!(
ContractName,
"ContractName",
CONTRACT_NAME_REGEX,
MAX_STRING_LEN,
RuntimeErrorType,
RuntimeErrorType::BadNameValue
RuntimeError,
RuntimeError::BadNameValue
);

impl StacksMessageCodec for ClarityName {
Expand Down
6 changes: 3 additions & 3 deletions clarity-types/src/tests/representations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

use rstest::rstest;

use crate::errors::RuntimeErrorType;
use crate::errors::RuntimeError;
use crate::representations::{
CONTRACT_MAX_NAME_LENGTH, CONTRACT_MIN_NAME_LENGTH, ClarityName, ContractName, MAX_STRING_LEN,
};
Expand Down Expand Up @@ -73,7 +73,7 @@ fn test_clarity_name_invalid(#[case] name: &str) {
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RuntimeErrorType::BadNameValue(_, _)
RuntimeError::BadNameValue(_, _)
));
}

Expand Down Expand Up @@ -157,7 +157,7 @@ fn test_contract_name_invalid(#[case] name: &str) {
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
RuntimeErrorType::BadNameValue(_, _)
RuntimeError::BadNameValue(_, _)
));
}

Expand Down
34 changes: 17 additions & 17 deletions clarity-types/src/tests/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use rstest::rstest;
use stacks_common::types::StacksEpochId;

use crate::Error;
use crate::errors::{CheckErrorKind, InterpreterError, RuntimeErrorType};
use crate::errors::{CheckErrorKind, InterpreterError, RuntimeError};
use crate::types::{
ASCIIData, BuffData, CharType, ListTypeData, MAX_VALUE_SIZE, PrincipalData,
QualifiedContractIdentifier, SequenceData, SequencedValue as _, StandardPrincipalData,
Expand Down Expand Up @@ -255,7 +255,7 @@ fn test_qualified_contract_identifier_local_returns_runtime_error() {
let err = QualifiedContractIdentifier::local("1nvalid-name")
.expect_err("Unexpected qualified contract identifier");
assert_eq!(
Error::from(RuntimeErrorType::BadNameValue(
Error::from(RuntimeError::BadNameValue(
"ContractName",
"1nvalid-name".into()
)),
Expand All @@ -264,53 +264,53 @@ fn test_qualified_contract_identifier_local_returns_runtime_error() {
}

#[rstest]
#[case::too_short("S162RK3CHJPCSSK6BM757FW", RuntimeErrorType::ParseError(
#[case::too_short("S162RK3CHJPCSSK6BM757FW", RuntimeError::TypeParseFailure(
"Invalid principal literal: Expected 20 data bytes.".to_string(),
))]
#[case::too_long("S1C5H66S35CSKK6CK1C9HP8SB6CWSK4RB2CDJK8HY4", RuntimeErrorType::ParseError(
#[case::too_long("S1C5H66S35CSKK6CK1C9HP8SB6CWSK4RB2CDJK8HY4", RuntimeError::TypeParseFailure(
"Invalid principal literal: Expected 20 data bytes.".to_string(),
))]
#[case::invalid_c32("II2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G", RuntimeErrorType::ParseError(
#[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(
#[case] input: &str,
#[case] expected_err: RuntimeErrorType,
#[case] expected_err: RuntimeError,
) {
let err =
PrincipalData::parse_standard_principal(input).expect_err("Unexpected principal data");
assert_eq!(Error::from(expected_err), err);
}

#[rstest]
#[case::no_dot("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0Gcontract-name", RuntimeErrorType::ParseError(
#[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", RuntimeErrorType::BadNameValue("ContractName", "1nvalid-name".into()))]
#[case::invalid_contract_name("SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G.1nvalid-name", RuntimeError::BadNameValue("ContractName", "1nvalid-name".into()))]

fn test_qualified_contract_identifier_parse_returns_interpreter_error(
#[case] input: &str,
#[case] expected_err: RuntimeErrorType,
#[case] expected_err: RuntimeError,
) {
let err = QualifiedContractIdentifier::parse(input)
.expect_err("Unexpected qualified contract identifier");
assert_eq!(Error::from(expected_err), err);
}

#[rstest]
#[case::no_dot("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-traitnft-trait", RuntimeErrorType::ParseError(
#[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", RuntimeErrorType::BadNameValue("ContractName", "1nvalid-contract".into()))]
#[case::invalid_trait_name("SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.valid-contract.1nvalid-trait", RuntimeErrorType::BadNameValue("ClarityName", "1nvalid-trait".into()))]
#[case::invalid_standard_principal("S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", RuntimeErrorType::ParseError(
#[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] input: &str,
#[case] expected_err: RuntimeErrorType,
#[case] expected_err: RuntimeError,
) {
let expected_err = Error::from(expected_err);

Expand All @@ -323,13 +323,13 @@ fn test_trait_identifier_parse_returns_runtime_error(
}

#[rstest]
#[case::bad_type_construction(".valid-contract.valid-trait", RuntimeErrorType::BadTypeConstruction)]
#[case::forwards_parse_errors("S162RK3CHJPCSSK6BM757FW.valid-contract.valid-trait", RuntimeErrorType::ParseError(
#[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] input: &str,
#[case] expected_err: RuntimeErrorType,
#[case] expected_err: RuntimeError,
) {
let err =
TraitIdentifier::parse_fully_qualified(input).expect_err("Unexpected trait identifier");
Expand Down
23 changes: 11 additions & 12 deletions clarity-types/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ pub use self::signatures::{
ListTypeData, SequenceSubtype, StringSubtype, StringUTF8Length, TupleTypeSignature,
TypeSignature,
};
use crate::errors::{
CheckErrorKind, InterpreterError, InterpreterResult as Result, RuntimeErrorType,
};
use crate::errors::{CheckErrorKind, InterpreterError, InterpreterResult as Result, RuntimeError};
use crate::representations::{ClarityName, ContractName, SymbolicExpression};
// use crate::vm::ClarityVersion;

Expand Down Expand Up @@ -189,7 +187,7 @@ impl QualifiedContractIdentifier {
pub fn parse(literal: &str) -> Result<QualifiedContractIdentifier> {
let split: Vec<_> = literal.splitn(2, '.').collect();
if split.len() != 2 {
return Err(RuntimeErrorType::ParseError(
return Err(RuntimeError::TypeParseFailure(
"Invalid principal literal: expected a `.` in a qualified contract name"
.to_string(),
)
Expand Down Expand Up @@ -277,7 +275,7 @@ impl TraitIdentifier {

pub fn parse_fully_qualified(literal: &str) -> Result<TraitIdentifier> {
let (issuer, contract_name, name) = Self::parse(literal)?;
let issuer = issuer.ok_or(RuntimeErrorType::BadTypeConstruction)?;
let issuer = issuer.ok_or(RuntimeError::BadTypeConstruction)?;
Ok(TraitIdentifier::new(issuer, contract_name, name))
}

Expand All @@ -291,7 +289,7 @@ impl TraitIdentifier {
) -> Result<(Option<StandardPrincipalData>, ContractName, ClarityName)> {
let split: Vec<_> = literal.splitn(3, '.').collect();
if split.len() != 3 {
return Err(RuntimeErrorType::ParseError(
return Err(RuntimeError::TypeParseFailure(
"Invalid principal literal: expected a `.` in a qualified contract name"
.to_string(),
)
Expand Down Expand Up @@ -398,10 +396,10 @@ impl SequenceData {
if let Value::Sequence(data) = &element {
let elem_length = data.len();
if elem_length != 1 {
return Err(RuntimeErrorType::BadTypeConstruction.into());
return Err(RuntimeError::BadTypeConstruction.into());
}
} else {
return Err(RuntimeErrorType::BadTypeConstruction.into());
return Err(RuntimeError::BadTypeConstruction.into());
}
}
if index >= seq_length {
Expand Down Expand Up @@ -575,7 +573,7 @@ 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(RuntimeErrorType::BadTypeConstruction.into()),
_ => return Err(RuntimeError::BadTypeConstruction.into()),
};
Ok(())
}
Expand Down Expand Up @@ -1403,10 +1401,11 @@ impl PrincipalData {
}

pub fn parse_standard_principal(literal: &str) -> Result<StandardPrincipalData> {
let (version, data) = c32::c32_address_decode(literal)
.map_err(|x| RuntimeErrorType::ParseError(format!("Invalid principal literal: {x}")))?;
let (version, data) = c32::c32_address_decode(literal).map_err(|x| {
RuntimeError::TypeParseFailure(format!("Invalid principal literal: {x}"))
})?;
if data.len() != 20 {
return Err(RuntimeErrorType::ParseError(
return Err(RuntimeError::TypeParseFailure(
"Invalid principal literal: Expected 20 data bytes.".to_string(),
)
.into());
Expand Down
Loading