Collects every call in a subtree as its resolved target and its argument list, from which
the caller reads what the delegation binds to the mintβs parameters.
The straight-line reading of a body: coverage once a guard has run on the path, pending
while a delegated mint has run with no guard before or after it yet, failed once a path
may leave the function successfully with such a mint standing, escaped once one may leave
before any guard ran.
An unsafe mint target and whether every recursive hop preserves the recipient and token it
receives. A callback guard needs both guarantees, while a code-less-recipient proof needs
only the first.
How a path established that the recipient can receive the mint. Callback evidence remains
valid when summarizing a guard helper, while a code-less proof must be retired once a call
could deploy code at the recipient address. Whether the evidence can cover a future mint is
tracked separately by GuardWalk::future_coverage.
The package-root directory names of the OpenZeppelin distributions: the npm scope and the
git-submodule roots. Provenance is judged against a full path component so a same-name
contract under a merely-substring path such as src/not-openzeppelin/ is not recognized.
The condition of a require/assert that passes only if the recipient can receive the token:
the recipient is proven code-less, the hook comparison succeeds, or the account short circuit
to.code.length == 0 || hook == sel accepts either case.
The argument a call binds to the calleeβs parameter at index. Named arguments come in
source order, which is not the parameter order: _mint({id: tokenId, to: to}) hands the
token to to all the same.
walk_guards on a calleeβs body, against the parameters the recipient and the token
landed on: the callee guards when a guard ran before any possible successful exit, which is
what its caller relies on. bypass says the wrapped body can leave through an assembly
return, making an uncovered placeholder such an exit. seen cuts recursion cycles.
Whether executing stmt always reverts, undoing everything the transaction did. Only a
revert counts, the guards being matched without an order: see may_return for the escapes
that leave the transaction standing.
Whether a resolved same-frame callable or one of its applied modifiers can reach assembly.
The recursion set is a path stack so independent calls are summarized independently.
Whether a statically known same-frame callable can create code, directly, through an applied
modifier, or through another internal call. Recursion cycles alone do not create code; any
opaque, virtual, or external state-changing call reached by the body remains conservative.
A call handing the checked identities to a same-frame function or modifier whose body proves
acceptance for the parameters they land on. Callback guards receive distinct recipient and
token identities; the recipient-only pass supplies the same identity in both slots, allowing
an address-only helper to prove that it has no code. Arguments are matched by is_exactly_var
against the calleeβs parameters and by parameter name for named arguments.
Whether a subtree can reach an assembly block in the same EVM frame, directly or through an
internal call. An assembly return leaves the frame without running a later revert or what
an outer modifier holds after its placeholder. Every assembly block is treated as capable of
doing so, conservatively matching may_return.
Whether an expression may change the code installed at an account. Pure and view calls are
stable (external ones execute through STATICCALL), while nonpayable/payable calls and
contract creation can run CREATE/CREATE2. Calls to the delegated mint itself are excluded:
the code-length proof is needed precisely until that call begins, though calls in its
arguments are still inspected.
mutates_var over an expression, for the condition of an if, which the If arm reaches
before the statement arm ever sees it: if ((tokenId = x) > 0) {} reassigns as surely as a
bare statement, and the condition runs whichever branch is taken.
A statement expression that guards the recipient and the token: require/assert on an
acceptance condition, or an internal call handing both to a helper that does. Only the
condition is read: a hook call sitting in the revert message decides nothing, and neither
does one in any other argument. An external helper would ask from a different contract and
cannot establish that the recipient accepts the minting contractβs callback.
Whether a recognized require/assert has another argument that may change account code.
The first argument is the closed-form acceptance condition itself; its receiver callback is
part of the proof. Message and other arguments are not, and Solidity does not guarantee they
run before the conditionβs code-length snapshot.
The OpenZeppelin contracts whose _mint skips the receiver check. ERC721 and
ERC721Upgradeable declare the unchecked _mint; in the v4 line, ERC721Consecutive and
ERC721ConsecutiveUpgradeable override it with a construction guard that forwards to the
base through super._mint, still without a receiver check, so resolving to them is just
as unsafe. In v5 the Consecutive extension overrides _update instead, and the two extra
names match nothing.
recipient.code.length compared against zero, for one polarity. Nothing else may ride along:
in to.code.length > 0 && id == 5 the second operand decides whether the branch runs.
The variable itself, behind parentheses and value-preserving conversions: to, (to),
IERC721Receiver(to), payable(to). Anything else, guardians[to] or
IERC721Receiver(other), is a different value, and asking it about the token says nothing
about the recipient.
recipient.onERC721Received(..., token, ...): the hook, asked of the recipient itself and
about the delegated token itself. A recipient may accept one token and refuse another, so an
answer about a different id, the hookβs third parameter, decides nothing for the minted one.
recipient.onERC721Received(...) <op> x, and nothing else. The comparison must be the whole
expression: in to == trusted || hook(to) == selector the hook never runs for trusted. The
other operand must be able to hold the accepting answer, and must not be a hook call itself,
which would compare the recipient against itself.
Whether an expression is the accepting answer. Comparing the hookβs answer against anything
else mints to a recipient that answered something the ERC721 receiver interface calls a
refusal, so only an operand this reads and finds equal to the selector is credited: the
literal, a conversion of it, a constant holding it, or a selector member resolving to
the receiver hook itself. The member is resolved rather than matched by name: spelled on a
same-name function of another shape, .selector is a different value, which a recipient
answering it never accepted with. A value settled at deployment or at runtime, an
immutable or a state variable, is unknown here and does not exempt.
Whether a resolved declaration is the ERC721 receiver hook: the exact name, the exact
(address, address, uint256, bytes) shape, and an externally callable declaration of a
non-library contract. The name alone would let a call to a same-name function of an
unrelated interface, which answers on a different selector, pass as the check, and an
attached library or free function runs in the minting contract without any external call,
so resolving to one says nothing about the recipient.
Whether a call dispatches through an internal function-pointer variable whose target is not
available from the callee type. Such a target may contain assembly that leaves the frame, so
exit analysis must treat the call conservatively.
Whether the variable cannot change between the delegation and the callback guard. An
intervening call, including one inside a recursively delegated override, can reenter and
mutate a state variable after the mint reads it but before the guard does. A local, a
parameter, a constant or an immutable cannot be moved that way.
Whether a statement may leave the function while keeping what the transaction already did.
A return does, and so does the EVM return/stop an assembly block can hold. Only the
statements that provably cannot leave answer no, so a guard is never credited on a path this
analysis does not read.
The statements before and after a modifierβs single top-level placeholder. More complicated
expansion shapes are left uncredited rather than guessing which paths execute the body.
Coverage in effect when a function body starts after expanding its modifiers in declaration
order. Prefixes are walked in execution order so calls in an inner modifier can retire an
outer code-length snapshot. A proven tail guard is represented as stable callback coverage
while walking the body: it runs after the body and can revert every mint the body made, unless
assembly in the body or an inner modifier can bypass it.
Whether a statement assigns to var: var = x, var += x, var++, delete var, or var
as a component of a tuple assignment. An assembly block is treated as an opaque assignment,
since it can rewrite Solidity locals outside the HIR expression tree. Identity here is by
variable, not by value, so a guard that checked var says nothing once var is reassigned:
the delegation then credits a value or an address the guard never saw, though the two spell
the same variable.
The expression carrying the same value, behind parentheses, payable(...) and casts to an
address or contract type, which map every operand to itself. A numeric cast such as
uint8(...) truncates: the minted address is then usually not the checked one, so it is
not peeled.
The declaration a call executes in the current EVM frame. A public function called by name
is internal here, while this.f() and other external calls run in another frame whose
assembly return cannot bypass the callerβs later statements.
Whether a cast preserves the recognized selectorβs value and byte alignment. A recognized
integer is exactly the positive selector, so any integer width of at least 32 bits keeps it.
Crossing between right-aligned integers and left-aligned fixed bytes is only trusted at the
four-byte boundary.
Reads a body in statement order and judges the delegated mints against the guards. A code-less
proof may cover a later delegation, but a callback must run after ownership is established to
match _safeMint: the receiver can inspect ownerOf, balances, or reenter during the hook.
Such a callback covers delegations still pending, the revert undoing them, unless a statement
in between may leave the function successfully, keeping the unacknowledged token:
super._mint(to, id); if (id == 0) return; require(hook...) walks out with token zero standing.