Skip to content

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

Description

@Lokkw0510

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions