crates/lint/docs/reentrancy-events.md
Severity: Low
ID: reentrancy-events
Flags emit statements that appear after an external call within the same function (or any internal helper it transitively calls). Emitting state-change events only after the external call returns can mislead off-chain consumers — including indexers, subgraphs, monitoring tools, and bridges — that rely on log ordering to reconstruct contract state.
For every function body, the lint performs a control-flow analysis that tracks whether an external call has occurred on the path leading to each statement. Only calls that can plausibly affect log ordering or observable state are considered — staticcall and high-level view / pure external calls are excluded. Tracked calls include:
address.call(...) and address.delegatecall(...) (with or without {value: ...} / {gas: ...} options).address.transfer(...), address.send(...).this.method(...) self-external calls.IERC20(token).transfer(...)). view and pure callees are not tracked.new Foo(...) (the constructor runs as an external interaction).External calls reached through internal/private/public helper functions, modifiers, and super.f(...) base-chain dispatch are tracked transitively when the helper is invoked by a bare identifier (e.g. _helper()) or via super.. Member-form internal dispatch such as Lib.f(...) and using for syntax is not yet followed; external calls hidden behind those forms may go undetected.
When the analysis encounters an emit statement reachable from a path that already executed a tracked external call, the statement is flagged.
Reentrancy and off-chain ordering both depend on event sequence:
Emitting the event before the external call ensures the log is anchored to the local state change, regardless of what the callee does.
contract BadCounter {
uint256 public counter;
event Counter(uint256 value);
function count(IExternal d) external {
counter += 1;
d.notify(); // external call first ...
emit Counter(counter); // ... then the event (may be reordered by reentrancy)
}
}
contract GoodCounter {
uint256 public counter;
event Counter(uint256 value);
function count(IExternal d) external {
counter += 1;
emit Counter(counter); // emit event right after the state change
d.notify(); // then perform the external call
}
}