argotorg / argotorg/solidity

`0 ** (-1)` is silently evaluated to `0` in a constant expression instead of being rejected as division by zero

Open Beginner friendly
#16,982 0 comments 0 reactions 0 assignees View on GitHub
bug :bug:
Dominant language
C++
Stars
25.7k
Forks
6.2k
Avg merge
1d 11h
Merged PRs (30d)
21

Description

## Description

`ConstantEvaluator`'s shortcut for exponentiation returns the base unchanged when the base is `0`
or `1`, without checking the sign of the exponent. For base `1` that is correct (`1 ** -1 == 1`),
and the neighbouring `-1` case is handled correctly too. For base `0` it is not: `0 ** -1` is
`1 / 0`, which is undefined, and the compiler quietly produces `0`.

The general path below the shortcut would have caught it — for a negative exponent it returns
`makeRational(denominator, numerator)`, which for base `0` is a rational with a zero denominator.

## Environment

- Compiler version: 0.8.36 (`develop`'s source is identical at these lines)
- Both pipelines (constant evaluation precedes codegen)

## Reproducer

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
contract C {
uint256 constant X = 0 ** (-1);
function f() external pure returns (uint256) { return X; }
}
```

**Expected:** rejected, the same way every other constant division by zero is.

**Actual:** compiles cleanly, no error and no warning, and `X` is `0`. The runtime bytecode is
byte-identical to the same contract written with `uint256 constant X = 0;`:

```
solc --bin-runtime --metadata-hash none zero-neg-exp.sol # identical output to `X = 0`
```

(`--metadata-hash none` only removes the trailing CBOR blob, which differs because the source text
differs; the code itself is the same.)

## Controls

All three of these behave correctly, which is what isolates the defect to base `0`:

```solidity
uint256 constant X = 1 / 0; // Error: Built-in binary operator / cannot be applied
// to types int_const 1 and int_const 0.
uint256 constant Y = 2 ** (-1); // rejected -- rational 1/2, not convertible to uint256
int256 constant Z = (-1) ** (-1); // == -1, correct
uint256 constant W = 1 ** (-1); // == 1, correct
```

`Z` and `W` were checked the same way and each compiles to bytecode identical to its literal, so the
two sign-independent bases really are handled correctly. The sign of the exponent is respected
everywhere except for base `0`.

## Cause

`libsolidity/analysis/ConstantEvaluator.cpp:117-128`:

```cpp
case Token::Exp:
{
if (_right.denominator() != 1)
return std::nullopt;
bigint const& exp = _right.numerator();

// x ** 0 = 1
// for 0, 1 and -1 the size of the exponent doesn't have to be restricted
if (exp == 0)
return 1;
else if (_left == 0 || _left == 1) // <-- no sign check on exp
return _left;
else if (_left == -1)
...
```

The comment explains why `0`, `1` and `-1` skip the precision guard — for those bases the
*magnitude* of the exponent does not matter. That reasoning is about size only; the *sign* was
carried along with it by accident. `1` and `-1` are genuinely sign-independent, `0` is not.

## Relationship to #16690

[#16690](https://github.com/argotorg/solidity/issues/16690) (closed) also reports invalid constant expressions being accepted,
but through a different mechanism: operands wrapped in an explicit cast
bypass the compile-time arithmetic checks. This case needs no cast — the literal expression
`0 ** (-1)` reaches the evaluator directly and the evaluator itself returns a value for it.

Contributor guide

Open the contributing guide

Research direction

Start at libsolidity/analysis/ConstantEvaluator.cpp:117-128 and trace the Token::Exp shortcut, then compile the provided 0 ** (-1) reproducer with solc. Done means the constant expression is rejected as division by zero while the listed control expressions retain their current behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, solidity
Domain
compilers
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
86/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.