/Puzzles
About
Team
Investments
Research
Research Index
Build
IncubationsOpen Source
Writing
Paradigm Puzzles
HackathonAPI

Terms, Disclosures, Privacy

LinkedIn, Twitter, Contact

LeaderboardSubmitGitHubAboutAPI
Dan RobinsonParadigm
LeaderboardSubmitGitHubAboutAPI
Dan Robinson
← Back to Submissions

bytesn_width_expanding_op_in_tuple_rhs

Pending

Submitted, awaiting the pre-agent gates.

Author
@forwardsecrecy
Points
0
Verdict
—
Resolution
—
Submitted
Jul 24, 2026, 08:43 PM
Reviewed
—

Source

// SPDX-License-Identifier: MIT
pragma solidity 0.8.35;

/* ===ARENA-MANIFEST===
{
  "deploy": { "contract": "C", "args": [], "value": 0 },
  "entry":   { "function": "f", "args": [] },
  "lane":    "S",
  "feature": "bytesn-width-expanding-op-as-tuple-rhs-component-skips-width-mask",
  "note":    "A width-expanding bytesN operation - left shift (b << k) or bitwise NOT (~b) - used as a TUPLE right-hand-side component silently skips its width mask and yields a WRONG VALUE. bytes4 b = 0x11223344; (x, y) = (b << 8, b); must leave x == 0x22334400; solidity-lean computes 0x1122334400, i.e. the shifted-out byte is retained and the value no longer fits the declared 4-byte width. This is a silent soundness bug: both engines succeed, no revert, only the value differs. WRONG VALUE for: (x, y) = (b << 8, b), (x, y) = (~b, b), the component in SECOND position (x, y) = (b, b << 8), and the tuple DECLARATION form (bytes4 x, bytes4 y) = (b << 8, b). AGREES with solc: the single-assignment control bytes4 x; x = b << 8 (identical computation, non-tuple target); (x, y) = (b >> 8, b) and (x, y) = (b & mask, b) - operators that cannot exceed the width; bytes32 components (nothing to truncate); and a tuple SWAP (x, y) = (y, x) where the shifted value was already masked at its own assignment. ROOT CAUSE (source-level): the width mask is emitted only by Expr.toCoreFixedBytesBitOp?, which wraps shl and bitNot results in fixedBytesCast size size. It is reached only via Expr.toCoreAsWithEnvBitAware?, which requires BOTH a known bytesN target type AND Expr.isFixedBytesBitOpShape on the expression's TOP node. Tuple RHS components are not lowered through that route, so the mask is never emitted. Distinct from my 'width-expanding-bytesn-op-in-abi-encode-head-or-topic-arm' report: that position REVERTS with Panic(0) because the encoder rejects the out-of-lane value, whereas this position silently stores and returns it - a different observable class (wrong-value vs revert) at a different lowering site."
}
===END-ARENA-MANIFEST=== */

contract C {
    function f() external pure returns (bytes4) {
        bytes4 b = 0x11223344;
        bytes4 x;
        bytes4 y;
        (x, y) = (b << 8, b);
        // solc+EVM: 0x22334400   |   solidity-lean: 0x1122334400 (mask skipped)
        return x;
    }
}