My notes are either copied or rewritten from : https://eips.ethereum.org/EIPS/eip-2771
This allows a contract that receives a request(meta-transaction) to check if the msg.sender is in a trusted Forwarders list, and if all checks pass it can swap out the msg.sender for the address approved by the transaction signer recieved as the last 20 bytes of the calldata.
A third party can authorise a meta-transaction and forward this to a Gas Relay (Forwarder).
- Transaction Signer: Signs & sends transactions to a Gas Relay
- Gas Relay: Receives signed requests off-chain from Transaction Signers and pays gas to turn it into a valid transaction that goes through a Trusted Forwarder
- Trusted Forwarder: A contract trusted by the Recipient to correctly verify signatures and nonces before forwarding the request from Transaction Signers
- Recipient: A contract that accepts meta-transactions through a Trusted Forwarder
Transaction Signer: Signs & sends transactions to a Gas Relay
Gas Relay: Receives signed requests off-chain from Transaction Signers and pays gas to turn it into a valid transaction that goes through a Trusted Forwarder
Trusted Forwarder: A contract trusted by the Recipient to correctly verify signatures and nonces before forwarding the request from Transaction Signers
Recipient: A contract that accepts meta-transactions through a Trusted Forwarder
- Check that the Forwarder is trusted.
- Extract the Transaction Signer address from the last 20 bytes of the call data and use that as the original sender of the transaction (instead of msg.sender)
- If the msg.sender is not a trusted forwarder (or if the msg.data is shorter than 20 bytes), then return the original msg.sender as it is.
The Recipient MUST
check that it trusts the Forwarder to prevent it from extracting address data appended from an untrusted contract. This could result in a forged address.
To provide this discovery mechanism a Recipient contract MUST
implement this function:
function isTrustedForwarder(address forwarder) external view returns(bool);
isTrustedForwarder MUST
return true if the forwarder is trusted by the Recipient, otherwise it MUST return false. isTrustedForwarder MUST NOT revert.
Internally, the Recipient MUST
then accept a request from forwarder.
isTrustedForwarder function MAY
be called on-chain, and as such gas restrictions MUST
be put in place.
It SHOULD NOT
consume more than 50,000 gas
A malicious forwarder may forge the value of _msgSender()
and effectively send transactions from any address.
Therefore, Recipient contracts must be very careful in trusting forwarders.
If a forwarder is upgradeable, then one must also trust that the contract won’t perform a malicious upgrade.
In addition, modifying which forwarders are trusted must be restricted, since an attacker could “trust” their own address to forward transactions, and therefore be able to forge transactions. It is recommended to have the list of trusted forwarders be immutable, and if this is not feasible, then only trusted contract owners should be able to modify it.