@@ -286,37 +286,52 @@ impl AssetMap {
286286 }
287287 }
288288
289- // This will get the next amount for a (principal, stx) entry in the stx table.
289+ /// This will get the next amount for a (principal, stx) entry in the stx table.
290290 fn get_next_stx_amount (
291291 & self ,
292292 principal : & PrincipalData ,
293293 amount : u128 ,
294294 ) -> Result < u128 , VmExecutionError > {
295+ // `ArithmeticOverflow` in this function is **unreachable** in normal Clarity execution because:
296+ // - Every `stx-transfer?` or `stx-burn?` is validated against the sender’s
297+ // **unlocked balance** before being queued in `AssetMap`.
298+ // - The unlocked balance is a subset of `stx-liquid-supply`.
299+ // - All balance updates in Clarity use the `+` operator **before** logging to `AssetMap`.
300+ // - `+` performs `checked_add` and returns `RuntimeError::ArithmeticOverflow` **first**.
295301 let current_amount = self . stx_map . get ( principal) . unwrap_or ( & 0 ) ;
296302 current_amount
297303 . checked_add ( amount)
298304 . ok_or ( RuntimeError :: ArithmeticOverflow . into ( ) )
299305 }
300306
301- // This will get the next amount for a (principal, stx) entry in the burn table.
307+ /// This will get the next amount for a (principal, stx) entry in the burn table.
302308 fn get_next_stx_burn_amount (
303309 & self ,
304310 principal : & PrincipalData ,
305311 amount : u128 ,
306312 ) -> Result < u128 , VmExecutionError > {
313+ // `ArithmeticOverflow` in this function is **unreachable** in normal Clarity execution because:
314+ // - Every `stx-burn?` is validated against the sender’s **unlocked balance** first.
315+ // - Unlocked balance is a subset of `stx-liquid-supply`, which is <= `u128::MAX`.
316+ // - All balance updates in Clarity use the `+` operator **before** logging to `AssetMap`.
317+ // - `+` performs `checked_add` and returns `RuntimeError::ArithmeticOverflow` **first**.
307318 let current_amount = self . burn_map . get ( principal) . unwrap_or ( & 0 ) ;
308319 current_amount
309320 . checked_add ( amount)
310321 . ok_or ( RuntimeError :: ArithmeticOverflow . into ( ) )
311322 }
312323
313- // This will get the next amount for a (principal, asset) entry in the asset table.
324+ /// This will get the next amount for a (principal, asset) entry in the asset table.
314325 fn get_next_amount (
315326 & self ,
316327 principal : & PrincipalData ,
317328 asset : & AssetIdentifier ,
318329 amount : u128 ,
319330 ) -> Result < u128 , VmExecutionError > {
331+ // `ArithmeticOverflow` in this function is **unreachable** in normal Clarity execution because:
332+ // - The inner transaction must have **partially succeeded** to log any assets.
333+ // - All balance updates in Clarity use the `+` operator **before** logging to `AssetMap`.
334+ // - `+` performs `checked_add` and returns `RuntimeError::ArithmeticOverflow` **first**.
320335 let current_amount = self
321336 . token_map
322337 . get ( principal)
@@ -1011,6 +1026,13 @@ impl<'a, 'b, 'hooks> Environment<'a, 'b, 'hooks> {
10111026 . expressions ;
10121027
10131028 if parsed. is_empty ( ) {
1029+ // `TypeParseFailure` is **unreachable** in standard Clarity VM execution.
1030+ // - `eval_read_only` parses a raw program string into an AST.
1031+ // - Any empty or invalid program would be rejected at publish/deploy time or earlier parsing stages.
1032+ // - Therefore, `parsed.is_empty()` cannot occur for programs originating from a valid contract
1033+ // or transaction.
1034+ // - Only malformed input fed directly to this internal method (e.g., in unit tests or
1035+ // artificial VM invocations) can trigger this error.
10141036 return Err ( RuntimeError :: TypeParseFailure (
10151037 "Expected a program of at least length 1" . to_string ( ) ,
10161038 )
@@ -1060,6 +1082,12 @@ impl<'a, 'b, 'hooks> Environment<'a, 'b, 'hooks> {
10601082 . expressions ;
10611083
10621084 if parsed. is_empty ( ) {
1085+ // `TypeParseFailure` is **unreachable** in standard Clarity VM execution.
1086+ // - `eval_raw` parses a raw program string into an AST.
1087+ // - All programs deployed or called via the standard VM go through static parsing and validation first.
1088+ // - Any empty or invalid program would be rejected at publish/deploy time or earlier parsing stages.
1089+ // - Therefore, `parsed.is_empty()` cannot occur for a program that originates from a valid Clarity contract or transaction.
1090+ // Only malformed input directly fed to this internal method (e.g., in unit tests) can trigger this error.
10631091 return Err ( RuntimeError :: TypeParseFailure (
10641092 "Expected a program of at least length 1" . to_string ( ) ,
10651093 )
@@ -1940,6 +1968,14 @@ impl<'a> LocalContext<'a> {
19401968
19411969 pub fn extend ( & ' a self ) -> Result < LocalContext < ' a > , VmExecutionError > {
19421970 if self . depth >= MAX_CONTEXT_DEPTH {
1971+ // `MaxContextDepthReached` in this function is **unreachable** in normal Clarity execution because:
1972+ // - Every function call in Clarity increments both the call stack depth and the local context depth.
1973+ // - The VM enforces `MAX_CALL_STACK_DEPTH` (currently 64) **before** `MAX_CONTEXT_DEPTH` (256).
1974+ // - This means no contract can create more than 64 nested function calls, preventing context depth from reaching 256.
1975+ // - Nested expressions (`let`, `begin`, `if`, etc.) increment context depth, but the Clarity parser enforces
1976+ // `ExpressionStackDepthTooDeep` long before MAX_CONTEXT_DEPTH nested contexts can be written.
1977+ // - As a result, `MaxContextDepthReached` can only occur in artificial Rust-level tests calling `LocalContext::extend()`,
1978+ // not in deployed contract execution.
19431979 Err ( RuntimeError :: MaxContextDepthReached . into ( ) )
19441980 } else {
19451981 Ok ( LocalContext {
@@ -2297,6 +2333,110 @@ mod test {
22972333 ) ;
22982334 }
22992335
2336+ #[ test]
2337+ fn asset_map_arithmetic_overflows ( ) {
2338+ let a_contract_id = QualifiedContractIdentifier :: local ( "a" ) . unwrap ( ) ;
2339+ let b_contract_id = QualifiedContractIdentifier :: local ( "b" ) . unwrap ( ) ;
2340+ let p1 = PrincipalData :: Contract ( a_contract_id. clone ( ) ) ;
2341+ let p2 = PrincipalData :: Contract ( b_contract_id. clone ( ) ) ;
2342+ let t1 = AssetIdentifier {
2343+ contract_identifier : a_contract_id,
2344+ asset_name : "a" . into ( ) ,
2345+ } ;
2346+
2347+ let mut am1 = AssetMap :: new ( ) ;
2348+ let mut am2 = AssetMap :: new ( ) ;
2349+
2350+ // Token transfer: add u128::MAX followed by 1 to overflow
2351+ am1. add_token_transfer ( & p1, t1. clone ( ) , u128:: MAX ) . unwrap ( ) ;
2352+ assert ! ( matches!(
2353+ am1. add_token_transfer( & p1, t1. clone( ) , 1 ) . unwrap_err( ) ,
2354+ VmExecutionError :: Runtime ( RuntimeError :: ArithmeticOverflow , _)
2355+ ) ) ;
2356+
2357+ // STX burn: add u128::MAX followed by 1 to overflow
2358+ am1. add_stx_burn ( & p1, u128:: MAX ) . unwrap ( ) ;
2359+ assert ! ( matches!(
2360+ am1. add_stx_burn( & p1, 1 ) . unwrap_err( ) ,
2361+ VmExecutionError :: Runtime ( RuntimeError :: ArithmeticOverflow , _)
2362+ ) ) ;
2363+
2364+ // STX transfer: add u128::MAX followed by 1 to overflow
2365+ am1. add_stx_transfer ( & p1, u128:: MAX ) . unwrap ( ) ;
2366+ assert ! ( matches!(
2367+ am1. add_stx_transfer( & p1, 1 ) . unwrap_err( ) ,
2368+ VmExecutionError :: Runtime ( RuntimeError :: ArithmeticOverflow , _)
2369+ ) ) ;
2370+
2371+ // commit_other: merge two maps where sum exceeds u128::MAX
2372+ am2. add_token_transfer ( & p1, t1. clone ( ) , u128:: MAX ) . unwrap ( ) ;
2373+ assert ! ( matches!(
2374+ am1. commit_other( am2) . unwrap_err( ) ,
2375+ VmExecutionError :: Runtime ( RuntimeError :: ArithmeticOverflow , _)
2376+ ) ) ;
2377+ }
2378+
2379+ #[ test]
2380+ fn eval_raw_empty_program ( ) {
2381+ // Setup environment
2382+ let mut tl_env_factory = tl_env_factory ( ) ;
2383+ let mut env = tl_env_factory. get_env ( StacksEpochId :: latest ( ) ) ;
2384+
2385+ // Call eval_read_only with an empty program
2386+ let program = "" ; // empty program triggers parsed.is_empty()
2387+ let err = env. eval_raw ( program) . unwrap_err ( ) ;
2388+
2389+ assert ! (
2390+ matches!(
2391+ err,
2392+ VmExecutionError :: Runtime ( RuntimeError :: TypeParseFailure ( msg) , _) if msg. contains( "Expected a program of at least length 1" ) ) ,
2393+ "Expected a type parse failure"
2394+ ) ;
2395+ }
2396+
2397+ #[ test]
2398+ fn eval_read_only_empty_program ( ) {
2399+ // Setup environment
2400+ let mut tl_env_factory = tl_env_factory ( ) ;
2401+ let mut env = tl_env_factory. get_env ( StacksEpochId :: latest ( ) ) ;
2402+
2403+ // Construct a dummy contract context
2404+ let contract_id = QualifiedContractIdentifier :: local ( "dummy-contract" ) . unwrap ( ) ;
2405+
2406+ // Call eval_read_only with an empty program
2407+ let program = "" ; // empty program triggers parsed.is_empty()
2408+ let err = env. eval_read_only ( & contract_id, program) . unwrap_err ( ) ;
2409+
2410+ assert ! (
2411+ matches!(
2412+ err,
2413+ VmExecutionError :: Runtime ( RuntimeError :: TypeParseFailure ( msg) , _) if msg. contains( "Expected a program of at least length 1" ) ) ,
2414+ "Expected a type parse failure"
2415+ ) ;
2416+ }
2417+
2418+ #[ test]
2419+ fn max_context_depth_exceeded ( ) {
2420+ let root = LocalContext {
2421+ function_context : None ,
2422+ parent : None ,
2423+ callable_contracts : HashMap :: new ( ) ,
2424+ variables : HashMap :: new ( ) ,
2425+ depth : MAX_CONTEXT_DEPTH - 1 ,
2426+ } ;
2427+ // We should be able to extend once successfully.
2428+ let result = root. extend ( ) . unwrap ( ) ;
2429+ // We are now at the MAX_CONTEXT_DEPTH and should fail.
2430+ let result_2 = result. extend ( ) ;
2431+ assert ! ( matches!(
2432+ result_2,
2433+ Err ( VmExecutionError :: Runtime (
2434+ RuntimeError :: MaxContextDepthReached ,
2435+ _
2436+ ) )
2437+ ) ) ;
2438+ }
2439+
23002440 #[ apply( test_clarity_versions) ]
23012441 fn vm_initialize_contract_already_exists (
23022442 #[ case] version : ClarityVersion ,
0 commit comments