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:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
contract C { uint256 constant X = (2 ** 2048) / (2 ** 2048); }
Rejected — one larger:
// 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:
std::size_t mostSignificantBaseBit = static_cast<std::size_t>(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.
Description
The precision guard for constant exponentiation computes the width of
base ** expasexp * (msb(base) + 1).msb()is a zero-based bit position, somsb + 1is the bit lengthof the base, and multiplying that whole length by the exponent charges the
+1adjustment once permultiplication rather than once overall.
The true width of
base ** expisfloor(exp * log2(base)) + 1, i.e. aboutexp * msb + 1.The guard therefore over-charges by roughly
exp - 1bits and rejects constant expressions that fitcomfortably inside the 4096-bit budget.
For base 2 (
msb == 1) the effect is worst: the guard charges2 * expbits, halving the usableexponent.
Environment
develop's source is identical at these lines)Reproducer
Accepted:
Rejected — one larger:
2 ** 2049is 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):2 ** 20482 ** 20492 ** 40952 ** 40964 ** 13654 ** 13663 ** 20483 ** 2049Accepted in every row iff
formula <= 4096, regardless of the true width. For base 2 the wholeexponent range 2049–4095 is wrongly refused.
Cause
libsolidity/analysis/ConstantEvaluator.cpp:48-65(fitsPrecisionExp), line 63:The
+1belongs outside the multiplication (_exp * mostSignificantBaseBit + 1), which is thestandard bound for the bit length of a power. The
mostSignificantBaseBit == 0early return for_base == 1shows the intent was a per-base adjustment, not a per-multiplication one.Called from
:140for theToken::Expcase.