feat(lint): add encode-packed-collision#14914
Conversation
Detects `abi.encodePacked()` calls with 2+ dynamic-type arguments
(`string`, `bytes`, `T[]`) which can produce hash collisions, e.g.
`encodePacked("a","bc") == encodePacked("ab","c")`.
mattsse
left a comment
There was a problem hiding this comment.
P2: Please fix member-call return inference before shipping. The current logic scans inherited contract items by method name and then bails unless there is exactly one match: encode_packed_collision.rs#L110-L126.
For concrete contracts that override inherited methods, e.g. name() / symbol(), both the base function and override can match, so abi.encodePacked(token.name(), token.symbol()) is missed even though both return string.
Suggested fix: resolve the actual called function using the call args / semantic function type instead of collecting by name only, or filter overridden base functions before the uniqueness check. Please also add a regression test with a concrete contract overriding inherited name() and symbol() that still warns.
ternary, and slices - Extend `contract_id_of` to handle interface/contract casts (`IERC20Metadata(addr).name()`) by inspecting the call callee for `Ident -> Contract` and `Type -> Contract` resolutions; add a general fallback through `expr_type` to cover struct-field and array-index receivers (`cfg.token.name()`, `tokens[i].name()`). - Thread `n_call_args: Option<usize>` through `call_return_type` so same-name overloads that differ only in arity are disambiguated before the dynamic-agreement check (`f()` vs `f(uint256)` now resolves correctly). - Handle `ExprKind::Slice` in `expr_type`; calldata slice `data[:4]` preserves the bytes type of the base expression. - Handle `ExprKind::Ternary` in `expr_type`; both branches must agree on dynamic-ness; if they do, the then-branch type is returned.
|
A few things worth addressing before merge, mostly around the hand-rolled type inference in Likely false negatives (silent miss on real bugs)The
Likely false positives worth refiningThe literal short-circuit (
A common mitigation other linters use: only count non-literal dynamics, or require ≥2 non-literal dynamic args. Minor soundness / style notes
Suggested additional fixtures// Currently MISSED — should warn
abi.encodePacked(msg.data, s);
abi.encodePacked(addr.code, s);
abi.encodePacked(abi.encode(a), abi.encode(b)); // no bytes() cast
abi.encodePacked(string.concat(a, b), c);
abi.encodePacked(bytes.concat(a, b), c);
abi.encodePacked(new bytes(10), s);
abi.encodePacked(flag ? a : "x", b);
abi.encodePacked(token.name(), s); // public string getter
abi.encodePacked(Strings.toString(x), Strings.toString(y)); // library
// Known FPs
abi.encodePacked("prefix:", s);
abi.encodePacked(s, s);
abi.encodePacked(uint256(bytes(a).length), a, uint256(bytes(b).length), b);Minimal fix suggestionIf solar exposes a resolved-callee or expression-type API, prefer that over the ad-hoc walker. If not, special-casing the handful of well-known Solidity builtins inside |
mablr
left a comment
There was a problem hiding this comment.
@grandizzy I fixed your findings in 8f60378
Description
Closes OSS-58
Add
encode-packed-collisionlint.Detects
abi.encodePacked()calls with 2+ dynamic-type arguments (string,bytes,T[]) which can produce hash collisions, e.g.encodePacked("a","bc") == encodePacked("ab","c").PR Checklist