EVM patterns
ERC2771 Forwarder Security: Trusted Context and Request Evidence
A recipient reads an appended sender only through a trusted forwarder with sufficient calldata. ERC2771 forwarder security requires authentic request verification, correct context consumption and application authorization on every relevant route.

Key facts
- Original recipient grid
- 32 labeled logical cases; 28 distinct caller/calldata/consumer inputs
- Context consumers
- 4 of 16 read suffix; 12 fall back to caller
- Raw consumers
- 16 of 16 retain actual caller identity
- Pinned signer scheme
- OpenZeppelin v5.4.0 forwarder uses ECDSA, not ERC1271
- Limit
- Interpretation model, no signature recovery, EVM or target call
Separate the relay from the effective sender
The transaction submitter, forwarder and effective sender can be different actors. ERC2771 forwarder security requires the recipient to interpret an appended signer address only when the actual caller is trusted, while the forwarder authenticates the request. A suffix that resembles an address is not evidence of authorization by itself.
A trusted forwarder is a contract that a recipient relies on to verify signed requests and nonces. It appends the signer's address to calldata before calling that recipient. The recipient checks the caller relationship before treating that suffix as the effective sender. The trust decision authorizes an interpretation of bytes; the request verification establishes a different part of the evidence.
The distinction is visible in source. OpenZeppelin's pinned ERC2771Context v5.4.0 extracts a sender only when the caller is trusted and calldata contains a complete context suffix. Otherwise it preserves ordinary msg.sender. Its paired forwarder verifies an EIP712 request using ECDSA recovery. These are specific implementations, not a universal claim about every ERC2771 forwarder's supported signature schemes.
| Observed actor | Meaning | Security boundary |
|---|---|---|
| Transaction submitter | The actor paying to submit the transaction | Not automatically the authorized application user |
| Recipient caller | The immediate contract invoking the recipient | Must be trusted before suffix interpretation |
| Appended sender | Identity supplied through the trusted context | Depends on the forwarder's authentic request check |
| Raw sender consumer | Code reading ordinary msg.sender | Can see the forwarder instead of the user |
The ERC2771 specification leaves the recipient's trust implementation open. It warns that a malicious trusted forwarder can forge the perceived sender and that an upgradeable forwarder's authority belongs in the trust analysis. Reviewing only the current signer recovery function would omit that deployment dependency.
Our signature replay guide explains the broader authorization-message problem. This article follows the evidence into the recipient: which sender each path reads, how the forwarder binds requests and what happens during batching or delegated execution.
A successful forwarder verification is not a successful business action. The target may reject its own preconditions or execute an action whose effects need independent checks. Keep request validity, low-level call outcome and application result as separate observations throughout testing and logging.
Original recipient context grid
Method
We modeled the pinned recipient's context interpretation over a complete labeled input grid. The caller was trusted or untrusted, calldata had a selected length, a synthetic suffix identity was supplied and the consuming path read either raw or context-aware sender. No signature recovery or application function executed.
- Source
- ERC2771 and OpenZeppelin Contracts
v5.4.0ERC2771Context.sol, retrieved on . - Axes
- Both caller-trust states, lengths
0,19,20and36bytes, two suffix labels and raw/context-aware consumers. - Population
32labeled Cartesian cases, with no exclusions. There are28distinct caller/calldata/consumer encodings because empty payloads cannot distinguish the two suffix labels.- Scope
- Sender interpretation only. The byte strings are not asserted to be valid ABI-dispatched business calls.
- Artifacts
seo/research/integration-boundaries-2026-10-11/support-execution/finite-models.pyanderc2771-forwarder-security-model-2026-10-11.json, plus all CSV rows. These repository artifacts are not public downloads.
Results
| Consumer observation | Cases | Denominator |
|---|---|---|
| Context consumer reads suffix | 4 | 16 context-aware cases |
| Context consumer falls back to caller | 12 | 16 context-aware cases |
| Raw consumer reads caller | 16 | 16 raw-consumer cases |
Only trusted callers with a complete suffix produced extraction. Length 19 exercised the short-payload fallback; length 20 exercised equality. Length 36 showed that the sender remains at the end of a longer payload. A complete suffix from an untrusted caller remained ordinary calldata.
The full labeled population contains 8 cases where raw and context-aware readings differ under the corresponding trust and length inputs. That difference identifies a code-review boundary. It is not a count of access-control defects, since an application can intentionally choose a particular identity for a particular operation.
Limits
- No EVM, signature verifier or target function ran. This is a finite source-derived interpretation model.
- The labeled denominator preserves the declared factors, including duplicate empty-payload encodings. The distinct-input count is reported separately.
- Suffix extraction does not establish authenticated signing, permitted business action or successful target execution.
The practical test is therefore two-part: establish why the recipient trusts the forwarder, then verify that every relevant application path reads the intended identity. A correct extraction helper cannot repair a privileged function that bypasses it.
Bind the forwarded request before execution
OpenZeppelin's pinned forwarder signs a request containing sender, target, native value, gas, nonce, deadline and data. It reads the current nonce from on-chain state when hashing. Its request verification checks recipient trust, deadline validity and matching recovered signer. Those checks approve the request boundary, not the target's resulting business state.
The source accepts a deadline equal to the current timestamp. An expiry test should exercise equality separately from the first expired value. Do not assert a stricter predicate because a frontend countdown rounded the displayed time differently. The authorization boundary belongs to the deployed contract.
- Request verification
- The forwarder checks its actual signed fields, nonce, deadline and recipient trust.
- Context interpretation
- The recipient validates caller trust and sufficient calldata before reading the suffix.
- Application authorization
- The target function checks that the effective actor may perform the requested action in current state.
This fixed forwarder uses ECDSA and does not call ERC1271. A contract wallet supported by another application is not automatically supported as a signer here. ERC2771 allows different forwarder designs, so the compatibility restriction belongs to the pinned implementation. Verify the actual signing population before promising contract-wallet support.
Single execution requires exact equality between supplied native value and the request's value. A valid request consumes its nonce before the target call. If the single target call returns failure, the outer execution reverts, restoring the nonce with the transaction state. Those source-level relationships need implementation tests in the actual integration.
Recipient trust is queried through a static call. OpenZeppelin's pinned forwarder's discovery predicate requires successful return, sufficient response data and a nonzero returned word. Record both the discovery observation and the recipient's configured trust. A provider failure should not silently become evidence that a recipient deliberately rejected the forwarder.
Trust-list updates and forwarder upgrades need a change owner. The standard does not prescribe one trust-management system. A mutable list, an immutable configured forwarder or an overridden trust function has different authority paths. Inspect the actual implementation and who can change the behavior users depend on.
Preserve the actual signing domain in that record. OpenZeppelin's pinned forwarder uses its supplied name and domain version 1 in EIP712 construction. A request assembled for another deployment or domain needs its own binding check. Similar request fields do not establish interchangeable authorization. Compare the displayed action, encoded domain and on-chain digest before approving compatibility between two relay integrations.
Inspect batching and delegated context
Forwarded context can become corrupted if an application self-delegates arbitrary calldata and then treats its tail as the original signer. The pinned context source warns about forwarded self-delegatecall. A reviewer must trace the suffix through nested execution, rather than assume that a correct outer extraction makes every inner call safe.
The pinned Multicall implementation handles a noncanonical context by appending the original suffix to its delegated subcalls. That is a specific integration pattern. Arbitrary custom self-delegatecalls need their own context-preservation argument. Copying one of these components without checking how the other constructs calldata is incomplete.
| Boundary | Required inspection | Unsupported shortcut |
|---|---|---|
| Nested sender recovery | Original context suffix reaches intended subcall | Outer extraction passed |
| Nested action filtering | Actual inner targets and selectors are permitted | Outer multicall selector was allowed |
| Batch native value | Supplied value equals request-value sum | Each individual request looked valid |
| Failure and refund behavior | Nonce, value and target state after each failure path | A variable is named atomic |
OpenZeppelin's Multicall documentation states that an outer selector filter does not automatically filter nested selectors. A relay service allowing one batch function may consequently allow more application actions than its top-level policy suggests. Review nested content and the recipient's own restrictions. A convenient transport wrapper should not broaden business permission unnoticed.
OpenZeppelin's pinned forwarder batch enforces aggregate native-value equality. A nonzero refund receiver permits skipping invalid requests instead of immediately reverting them. A zero refund receiver selects the path requiring valid requests. Keep that validity condition separate from the outcome of an already valid target call.
There is a source-comment boundary worth preserving in a review. In v5.4.0, the batch variable named atomic controls request-validity enforcement, while the loop accumulates value when execution reports failure. The loop does not itself unconditionally revert solely because a valid top-level target call returns false. Do not approve an all-or-nothing target rollback guarantee from the variable name or broad comment alone.
This is a bounded source observation, not an executed vulnerability finding. The integration needs a regression test for valid-request target failure, invalid-request handling, nonce state and refund behavior under its actual configuration. Other checks or nested behavior may affect the final transaction outcome; source interpretation should not be reported as an observed exploit.
Pharos Production describes reviewing access-control gaps and logic flaws in its smart contract authorization and execution-logic audit service. That scope is semantically close to trusted-context consumption and nested action checks. Specify the forwarder, recipient and batching composition in the engagement; the service page does not claim that a particular ERC2771 deployment has already been reviewed.
Test rejection and recipient failure separately
A rejection test should preserve the surrounding valid conditions. Start with a request the intended signer, forwarder and recipient accept. Then change caller trust, suffix length, nonce, deadline or signed action separately. Inspect both the rejecting gate and the state that remains afterward.
- Pin the forwarder and recipient implementations, trust configuration and supported signer scheme.
- Send an untrusted caller's appended address and confirm the recipient keeps ordinary caller identity. Repeat a trusted call with a short payload to exercise the length boundary.
- Execute a valid forwarded action through every relevant mutation route. Verify that its access control consumes the intended identity.
- Change a material signed field, reuse a consumed nonce and cross the deadline boundary with otherwise accepted fixtures.
- Test single target failure, invalid batch requests and valid-request target failure separately. Inspect nonce state, value movement and refunds.
- Exercise nested selectors and self-delegatecalls with context-sensitive authorization. Preserve positive controls showing intended composition still works.
Pay attention to raw calldata-length checks. OpenZeppelin's context implementation warns that appending a suffix can affect code that selects behavior based on exact calldata length, including receive or fallback paths. A gas-sponsored call that appears empty at the application level is not necessarily empty in raw forwarded bytes. Test the actual recipient dispatch.
A native refund is also a call surface. A refund receiver can affect control flow according to the actual implementation. Specify who receives refunds and test its failure behavior in the deployed batch path. The context grid contains no value movement and cannot establish this property.
Gas delivery has a separate source check measured immediately after the target call. Its presence does not establish that the target's business action succeeded. The operator should record the requested gas, low-level outcome and interpreted application result separately. A successful request-verification API can precede a target that rejects current state.
The transaction simulation guide explains how to preserve state and input assumptions for these rehearsals. A simulation should use the actual forwarding route rather than call the recipient directly as the signer. The direct call would bypass the context transformation under review.
A native refund can arrive with empty calldata. OpenZeppelin's pinned forwarder describes that call shape, so a refund recipient that also trusts the forwarder needs to handle length zero correctly. The current context helper falls back to the actual caller in that case. A trust relationship does not create a nonexistent sender suffix.
Use a recipient that deliberately rejects the refund as a separate failure fixture. Inspect the outer transaction outcome, refund amount and nonce state rather than assuming that a failed target and failed refund have the same result. These are proposed integration tests; the sender-interpretation grid moved no value.
| Request path | Nonce boundary | Remaining condition |
|---|---|---|
| Invalid request allowed to skip | Rejected before valid-request nonce consumption | Outer batch and refund behavior |
| Valid request and successful target | Nonce consumed before target call | Application result and any later outer revert |
| Valid request and failed target | Nonce consumption was reached | Final persistence depends on enclosing outcome |
The valid-failed row should be retested using the resulting nonce only after establishing the outer outcome. If a later outer revert restores state, an intermediate source step is not evidence that the nonce remained consumed. If the outer transaction succeeds, inspect the stored nonce directly. The evidence needs to connect the control-flow observation to the final committed state.
Also review the trust method itself. The pinned context stores a configured immutable forwarder, but its virtual trust accessors can be overridden in a composed recipient. An immutable field alone does not establish that every derived trust decision is fixed. Preserve the actual recipient implementation and its override paths in the trust record.
Record the trust boundary before enabling relays
The acceptance record should identify supported signers, signed fields, nonce and deadline behavior, recipient trust and the identity consumed by privileged routes. Include batching and delegated context tests where they exist. Separate request validity from target success and application invariants in the public status model.
The security firm directory and Pharos Production member profile support selecting a reviewer. Give that reviewer the full relay composition and its authority configuration. A standalone ECDSA check cannot establish that the recipient interprets the suffix correctly or that nested actions remain within policy.
Our original model establishes an interpretation result: 4 of 16 context-aware labeled cases read the suffix, while the others fall back. The distinct-input count and model limits remain attached to that result. It should not be translated into a claim that only a certain fraction of real requests are secure.
Enable the relay only when the evidence connects request authentication, trusted context and business authorization. Revisit that connection after a trust update, forwarder upgrade or new batching path. The effective sender is useful because its provenance is explicit, and that provenance needs to survive the entire execution route.
Frequently asked questions
What does ERC2771 require of trust discovery gas?
Its isTrustedForwarder discovery method must not revert and should use at most 50000 gas. The gas language is SHOULD, not a mandatory universal transaction budget.
Can one recipient support different forwarding formats?
The standard permits trusted forwarders with different signing and batching formats. Each trusted implementation still needs its own request and authority review.
Does adding ERC2771 create a privacy guarantee?
The interface changes how a recipient interprets an authenticated sender context. It does not establish anonymity, unlinkability or a privacy property for the application.