Add detector for msg.value usage unreachable from payable entry points#2867
Add detector for msg.value usage unreachable from payable entry points#2867sidarth16 wants to merge 11 commits intocrytic:masterfrom
msg.value usage unreachable from payable entry points#2867Conversation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ep0chzer0
left a comment
There was a problem hiding this comment.
Review: Implementation Feedback
Thanks for working on this, the core detection logic is correct! However, I found a few issues that need to be addressed:
1. Output Format Issue (Critical)
The _build_info method uses strings instead of Slither objects, which breaks the JSON/SARIF output format:
Current (incorrect):
info = [
"msg.value used in non-payable context\n",
f" Location: {func.source_mapping.filename.short}:{func.source_mapping.lines[0]}\n",
...
]Expected (per Slither conventions):
info: DETECTOR_INFO = [
func,
" uses msg.value but is not reachable from any payable function\n",
]
if non_payable_callers:
info.append("\tNon-payable entry points that can reach this function:\n")
for caller in non_payable_callers:
if caller != func:
info.extend(["\t- ", caller, "\n"])The Output class expects Slither objects (Function, Node, etc.) which it converts to proper source mappings for IDE integration and SARIF output.
2. Missing Import
You need to import DETECTOR_INFO from the abstract detector:
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)3. Include the Function Itself as Entry Point
In _get_entry_point_callers, you should include the function itself if it's public/external:
if function.visibility in ("public", "external"):
if function.payable:
payable_callers.append(function)
else:
non_payable_callers.append(function)4. Missing is_implemented Check
Add a check to skip functions that aren't implemented:
if not func.is_implemented:
continue5. Missing Test Cases
The PR needs test data. Note that in Solidity 0.8+, the compiler prevents direct msg.value use in non-payable public/external functions, so tests should focus on internal functions that use msg.value but are only reachable from non-payable entry points.
Happy to help with any questions!
|
Hi @ep0chzer0 , I’ve now addressed the points you raised:
Here is the updated detector output now when running on the I will also be adding the
|
3f29cb8 to
7c0d32e
Compare
|
Hi @smonicas and @ep0chzer0 Could you please guide me on the expected next steps to integrate these tests correctly? Thanks! |
|
Hi @sidarth16, thanks for addressing the feedback - the changes look good! Regarding the test setup: yes, you'll need to generate the snapshot files. The process is:
For the zip artifacts, you can use Docker if you don't have the right solc version: docker run --rm -v $(pwd):/src -w /src --platform linux/amd64 ghcr.io/crytic/crytic-compile:latest crytic-compile tests/e2e/detectors/test_data/msg-value-in-nonpayable/0.8.0/msg_value_in_nonpayable.sol --export-zip msg_value_in_nonpayable.sol-0.8.0.zipNote to maintainers (@elopez, @smonicas): I also submitted PR #2923 for the same issue (#2781) before noticing sidarth16 had already addressed my review feedback here. Happy to close #2923 if you'd prefer to proceed with this PR - just let me know what works best for the project. |

Summary
This PR resolves #2781 and introduces a new Slither detector,
msg-value-in-nonpayable, which identifies uses ofmsg.valuein functions that can never be reached from any payable public or external entry point.In such cases,
msg.valueis provably meaningless:This detector helps developers catch logic errors, dead code, and incorrect assumptions about ETH flow early.
Detection Logic
The detector:
msg.value(including via modifiers)Only the function that uses
msg.valueis reported; callers are included for explanatory context.Example
Here,
subscribe()is not payable and cannot receive ETH.As a result,
msg.valueis always zero and therequirestatement always fails.Notes
The detector follows the semantics discussed in #2781 and reports only
msg.valueusages that are provably unreachable from any payable execution path.