argotorg / argotorg/solidity

`fitsPrecisionExp` charges the base's bit *length* per multiplication instead of its bit *position*, so valid constant powers such as `2 ** 2049` are rejected

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

Description

## Description

The precision guard for constant exponentiation computes the width of `base ** exp` as
`exp * (msb(base) + 1)`. `msb()` is a zero-based bit *position*, so `msb + 1` is the bit *length*
of the base, and multiplying that whole length by the exponent charges the `+1` adjustment once per
multiplication rather than once overall.

The true width of `base ** exp` is `floor(exp * log2(base)) + 1`, i.e. about `exp * msb + 1`.
The guard therefore over-charges by roughly `exp - 1` bits and rejects constant expressions that fit
comfortably inside the 4096-bit budget.

For base 2 (`msb == 1`) the effect is worst: the guard charges `2 * exp` bits, halving the usable
exponent.

## Environment

- Compiler version: 0.8.36 (`develop`'s source is identical at these lines)
- The guard runs during constant evaluation, so both pipelines are affected

## Reproducer

Accepted:

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
contract C { uint256 constant X = (2 ** 2048) / (2 ** 2048); }
```

Rejected — one larger:

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
contract C { uint256 constant X = (2 ** 2049) / (2 ** 2049); }
```

```
Error: Built-in binary operator ** cannot be applied to types int_const 2 and int_const 2049.
--> rejected.sol:3:36:
|
3 | contract C { uint256 constant X = (2 ** 2049) / (2 ** 2049); }
| ^^^^^^^^^
```

`2 ** 2049` is a 2050-bit number and the budget is 4096 bits, so it should be accepted.
(The division keeps the *result* a `uint256`; the intermediate is what the guard is judging.)

## Measured boundary

Acceptance tracks the formula exactly rather than the real width. `formula = exp * (msb(base) + 1)`,
`true = floor(exp * log2(base)) + 1`, budget 4096 (solc 0.8.36):

| expression | formula | true width | result |
|---|---|---|---|
| `2 ** 2048` | 4096 | 2049 | accepted |
| `2 ** 2049` | 4098 | 2050 | **rejected** |
| `2 ** 4095` | 8190 | 4096 | **rejected** |
| `2 ** 4096` | 8192 | 4097 | rejected (correct — genuinely over budget) |
| `4 ** 1365` | 4095 | 2731 | accepted |
| `4 ** 1366` | 4098 | 2733 | **rejected** |
| `3 ** 2048` | 4096 | 3247 | accepted |
| `3 ** 2049` | 4098 | 3248 | **rejected** |

Accepted in every row iff `formula <= 4096`, regardless of the true width. For base 2 the whole
exponent range 2049–4095 is wrongly refused.

## Cause

`libsolidity/analysis/ConstantEvaluator.cpp:48-65` (`fitsPrecisionExp`), line 63:

```cpp
std::size_t mostSignificantBaseBit = static_cast(boost::multiprecision::msb(_base));
if (mostSignificantBaseBit == 0) // _base == 1
return true;
if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096
return false;

bigint bitsNeeded = _exp * (mostSignificantBaseBit + 1); // <-- the +1 is inside the product

return bitsNeeded <= bitsMax;
```

The `+1` belongs outside the multiplication (`_exp * mostSignificantBaseBit + 1`), which is the
standard bound for the bit length of a power. The `mostSignificantBaseBit == 0` early return for
`_base == 1` shows the intent was a per-base adjustment, not a per-multiplication one.

Called from `:140` for the `Token::Exp` case.

Contributor guide

Open the contributing guide

Research direction

Start in libsolidity/analysis/ConstantEvaluator.cpp at fitsPrecisionExp, then inspect its Token::Exp call site around line 140. Compile the supplied 2 ** 2048 and 2 ** 2049 reproducers and compare the measured boundaries; done means powers within the 4096-bit budget are accepted while genuinely oversized powers remain rejected.

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
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.