shift amount is reduced modulo 2^(width of left operand), so `int8(5) >> 256` is proved equal to 5
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
Research direction
The issue points to libsolidity/formal/SMTEncoder.cpp:1960, in bitwiseOperation, and includes a Solidity reproducer in C.sol. Read the shift encoding and run the supplied run.sh with solc and the EVM oracle available. Done means the SMTChecker reports the assertion violation for the reproducer and its result agrees with EVM behavior for the described shift cases.
Written by the indexing model from the issue text.
Description
Found by comparing the semantics described in the Solidity docs with how the Solidity SMTChecker implements them. An AI agent found this bug, reproduced it with solc 35b1cb80 (develop) and drafted this report. A second agent tried to refute it and searched upstream for duplicates. The methodology is described here.
Target detail: solc SMTChecker (CHC and BMC)
What went wrong: Wrong result (a failing assertion is reported safe; counterexamples that do not replay)
Version: 0.8.37+commit.f401782d (static linux release). Also reproduces at develop nightly 0.8.38-nightly.2026.10.19+commit.4c8a81af (soljson, CHC).
Preconditions: SMTChecker enabled (CHC default engine, or BMC); shift amount of a wider type than the left operand that can reach ≥ 2^(bits of left operand), e.g. a uint256 shifting an int8/uint8/bytes1 (≥ 256) or a uint32/bytes4 (≥ 2^32).
Input
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract C {
function f(int8 a, uint256 s) public pure {
require(a == 5 && s == 256);
int8 r = a >> s;
assert(r == a);
}
}
Command
SOLC=/path/to/solc ./run.sh # exit 1 = bug reproduced, 0 = fixed, 2 = tool missing / harness error
solc --model-checker-engine chc --model-checker-targets assert --model-checker-show-proved-safe C.sol
Output
Info: CHC: Assertion violation check is safe!
--> C.sol:7:5:
|
7 | assert(r == a);
| ^^^^^^^^^^^^^^
Command
# runtime bytecode of C.sol (solc --bin-runtime), calldata f(5, 256)
geth evm / revm: run --code <runtime> --calldata a4797c9b<int8 5><uint256 256>
Output
{"tool": "geth", "fork": "osaka", "status": "revert", "returndata": "0x4e487b710000000000000000000000000000000000000000000000000000000000000001", "gas_used": 22047, "logs": [], "storage": {}, "error": "execution reverted"}
{"tool":"revm","fork":"osaka","status":"revert","returndata":"0x4e487b710000000000000000000000000000000000000000000000000000000000000001","gas_used":22047,"logs":[],"storage":{},"error":"execution reverted"}
Expected
An assertion violation for f(5, 256). Docs (Types > Integers > Shifts): "x >> y is equivalent to the mathematical expression x / 2**y, rounded towards negative infinity", so 5 >> 256 == 0; EIP-145 gives 0 for SAR by ≥ 256, and the compiled code panics with 0x01 as shown.
With s == 255 the violation is reported correctly. Also affected: uint8(1) << s, bytes1 >> s, int16 >> 65536, uint32/bytes4 >> 2**32, >>=/<<=, literal a >> 256, BMC as well. The converse also happens: for uint8 r = x << s; assert(r == 0 || s < 8) with x == 1, CHC reports the counterexample s = 258, r = 4, which does not fail on the EVM. Location: libsolidity/formal/SMTEncoder.cpp:1960 (bitwiseOperation): int2bv(_right, bvSize) with bvSize the width of the left operand's type.
Reproducer files
C.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract C {
function f(int8 a, uint256 s) public pure {
require(a == 5 && s == 256);
int8 r = a >> s;
assert(r == a);
}
}
oracle.sh
# Concrete EVM oracle: go-ethereum's `evm` tool (set EVM=/path/to/evm; default: from PATH).
# oracle RUNTIME_HEX [CALLDATA_HEX] [VALUE_WEI] -> one JSON line {status, error, returndata, storage, logs}; rc 2 on tool error.
EVM="${EVM:-evm}"
oracle() {
local o e rc g=(); o=$(mktemp /tmp/oracle-o.XXXXXX); e=$(mktemp /tmp/oracle-e.XXXXXX)
if [ "${3:-0}" != 0 ]; then # fund the sender so a value transfer is possible
g=(--prestate "$o.g" --sender 0x0000000000000000000000000000000000000001)
echo '{"config":{"chainId":1,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"mergeNetsplitBlock":0,"shanghaiTime":0,"cancunTime":0,"pragueTime":0,"terminalTotalDifficulty":0,"blobSchedule":{"cancun":{"target":3,"max":6,"baseFeeUpdateFraction":3338477},"prague":{"target":6,"max":9,"baseFeeUpdateFraction":5007716}}},"alloc":{"0x0000000000000000000000000000000000000001":{"balance":"0xffffffffffffffff"}},"difficulty":"0x0","gasLimit":"0x1000000","timestamp":"0x0"}' > "$o.g"
fi
"$EVM" run --gas 10000000 --input "${2:-}" --value "${3:-0}" "${g[@]}" --dump --trace "$1" >"$o" 2>"$e"; rc=$?
[ $rc -eq 0 ] && python3 -I - "$o" "$e" <<'PY'
import json,sys
o,e=open(sys.argv[1]).read(),open(sys.argv[2]).read()
tr=[json.loads(l) for l in e.splitlines() if l.startswith('{')]
last=tr[-1]; err=last.get("error","")
logs=sum(1 for t in tr if str(t.get("opName","")).startswith("LOG"))
accts=json.loads(o[o.index('{'):]).get("accounts",{})
st={}
for a in accts.values(): st.update(a.get("storage",{}))
print(json.dumps({"status":"error" if err else "success","error":err,"returndata":"0x"+last.get("output",""),"storage":st,"logs":logs}))
PY
rc=$?; rm -f "$o" "$o.g" "$e"; [ $rc -eq 0 ] || echo "harness error: evm run failed" >&2; return $rc
}
run.sh
#!/usr/bin/env bash
# Repro: solc SMTChecker proves an assertion safe that fails (Panic 0x01) on geth.
# exit 1 = bug present (SMTChecker "safe" AND oracles show Panic(1)), 0 = correct, 2 = harness error.
set -u
D="$(cd "$(dirname "$0")" && pwd)"
# Tools (override via env; defaults are looked up on PATH): SOLC, EVM (go-ethereum `evm`, the concrete oracle).
S="$(command -v "${SOLC:-solc}")" || { echo "harness error: solc not found (set SOLC)"; exit 2; }
source "$D/oracle.sh"
"$S" --version | tail -n 1
CALLDATA_ARGS="00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000100"
smt_bug=0
for eng in chc bmc; do
out="$("$S" --model-checker-engine $eng --model-checker-targets assert,underflow,overflow,divByZero,outOfBounds \
--model-checker-show-proved-safe --model-checker-show-unproved "$D/C.sol" 2>&1)" || { echo "harness error: solc"; exit 2; }
echo "--- SMTChecker $eng:"; echo "$out" | grep -E "^(Info|Warning): (CHC|BMC)"
echo "$out" | grep -q "Assertion violation check is safe" && smt_bug=$((smt_bug+1))
done
code="$("$S" --bin-runtime "$D/C.sol" | tail -n 1)"
sel="$("$S" --hashes "$D/C.sol" | grep -oE '^[0-9a-f]{8}: f\(' | cut -c1-8)"
[ -n "$code" ] && [ -n "$sel" ] || { echo "harness error: compile"; exit 2; }
cd_hex="$sel$CALLDATA_ARGS"
echo "--- concrete run f(...) calldata 0x$cd_hex"
panic=0
for o in geth; do
j="$(oracle "$code" "$cd_hex")" || { echo "harness error: $o"; exit 2; }
echo "$o: $j"
echo "$j" | grep -q '"returndata": *"0x4e487b710000000000000000000000000000000000000000000000000000000000000001"' && panic=$((panic+1))
done
echo "SMTChecker engines claiming safe: $smt_bug/2; oracles failing the assert (Panic 0x01): $panic/1"
if [ $smt_bug -gt 0 ] && [ $panic -eq 1 ]; then echo "BUG: assertion proved safe but fails on EVM"; exit 1; fi
if [ $panic -ne 1 ] && [ $panic -ne 0 ]; then echo "harness error: oracles disagree"; exit 2; fi
exit 0
If this is intended behaviour or a duplicate, feel free to close it, and I'll use that as feedback to improve future reports. For questions, or to have this re-checked on a newer version of the Solidity SMTChecker, mention @JeroenRobben or email [email protected].
- Dominant language
- C++
- Stars
- 25.8k
- Forks
- 6.2k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 43
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from argotorg/solidity
-
AI tool policy: add AGENTS.mdMay be free again @cameel claimed this 47 days ago, and no pull request is open. Opendocumentation :book:
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
argotorg/solidity#16996 · 2 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
`fitsPrecisionExp` charges the base's bit *length* per multiplication instead of its bit *position*, so valid constant powers such as `2 ** 2049` are rejectedPossibly taken @clonker claimed this 54 days ago. Openbug :bug:
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
argotorg/solidity#16984 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
`0 ** (-1)` is silently evaluated to `0` in a constant expression instead of being rejected as division by zeroMay be free again @matheusaaguiar claimed this 54 days ago, and no pull request is open. Openbug :bug: low impact
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
argotorg/solidity#16982 · 3 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
AssemblyItem AssemblyItem::toSubAssemblyTag assumes size_t is at least 64bPossibly taken @riba2534 claimed this 67 days ago. Openbug :bug: low impact
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
FileReader: Duplicate search paths cause false ambiguous importsPossibly taken @zjuncal claimed this 2 days ago. Openbug :bug:
Difficulty 2/5 1-3 hours Newbie friendliness 35/100
Maintainers usually reply within 1 day
All issues in argotorg/solidity
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
google/tensorstore#306 ·
-
agent:WSL bug code-review linux pri:medium sev:low tooling
Difficulty 2/5 Half a day Newbie friendliness 62/100
Maintainers usually reply within 1 day