Approvals and permissions
How Forensia reads token approvals, spots an approval followed by a transfer, and avoids claiming the approval was used.
#Plain languageIn plain terms
Before a service can move your tokens, you approve it. An approval is a standing permission: "this address may move up to this much of my token". Many thefts do not steal a key at all. They trick someone into signing an approval, then use it.
Forensia lists the approvals an address granted and highlights where one is followed by a transfer of the same token out of the same wallet. It stops short of saying the approval caused the transfer, because the records alone cannot show that.
#How it worksHow it works
- Find approval events in the receipt, or in a window of recent blocks for an address.
- Decode each one: who granted, to whom, for which token, and how much.
- Put approvals and transfers in order.
- Flag an approval that comes before a compatible transfer out of the same wallet.
- For the newest few, ask the token contract whether the permission is still in force.
#Technical detailThe exact rules
| Event | Layout | Meaning |
|---|---|---|
Approval | 3 topics, one data word | ERC-20 allowance. Granted when the amount is above zero. |
Approval | 4 topics, empty data | ERC-721 approval for one token ID. |
ApprovalForAll | 3 topics, data is 0 or 1 | Operator approval over a whole collection. |
A transfer is compatible with an earlier approval when it comes later in the log order, is on the same token contract, is sent from the approving owner, and matches the standard. It must also come before any later change to the same permission. An ERC-721 approval for one token stops matching after that token's first transfer, which clears it.
| Limit | Value |
|---|---|
| Approval events kept | 48 |
| Sequences reported | 24 |
| Current-state checks | 4 |
Current state is read with allowance, getApproved or isApprovedForAll at the latest block and shown as active, inactive or unavailable.
Matching the call that used it
Where a call trace exists, Forensia looks for a transferFrom call (selector 0x23b872dd) made by the approved address to the token contract, with the same owner, recipient and amount. One such call is reported as a matching call. More than one is reported as ambiguous. A call that returned false is reported as such.
This check covers ERC-20 approvals and transfers inside the transaction that was looked up. The call must be a plain CALL with no value attached that did not fail, the transfer must not exceed the approved amount, and no other change to the same permission may sit between the two events. It runs only for Ethereum mainnet transactions of type 0 to 4 whose block matches the pinned fork schedule. Otherwise the result is "Spending call not established".
The call's return value is read when it was kept: true, false, empty or unknown. Empty or missing return data is not assumed to mean success. When no call matches, the comparison reads "Approval is not proof of spending".
In the interface the comparisons are the Analysis section "Permissions and later transfers". When exactly one call matches, the comparison links to that exact step in the transaction steps.
Findings: finding:authorization-surface for one transaction and finding:authorization-chronology for an address. Both are class lead when a sequence exists.
#LimitsWhat it does not tell you
- Signed permits and other off-chain authorisations are not decoded. Only the two on-chain approval events are read.
- A matching call is supporting context. It is not proof that this allowance was the one consumed.
- Past allowance amounts are not reconstructed. Only the latest state is read, for a few approvals.
- Approving a service is normal. Most approvals are followed by transfers for ordinary reasons.