Hi, I'm a bot! This change was automatically merged because: - It only modifies existing Draft, Review, or Last Call EIP(s) - The PR was approved or written by at least one author of each modified EIP - The build is passing
12 KiB
eip | title | author | discussions-to | status | type | category | created |
---|---|---|---|---|---|---|---|
3074 | Sponsored Transaction Precompile | Sam Wilson (@SamWilsn) | https://ethereum-magicians.org/t/eip-3074-sponsored-transaction-precompile/4880 | Draft | Standards Track | Core | 2020-10-15 |
Simple Summary
Creates a new precompile, analogous to CALL
(0xF1
), that sets CALLER
(0x33
) and ORIGIN
(0x32
) based on an ECDSA signature.
Abstract
This EIP creates a single precompile (CALL_PRECOMPILE
) which forwards a CALL
, setting CALLER
and ORIGIN
according to an ECDSA signature.
Motivation
Sponsored transactions—the separation of fee payment from transaction content—have been a long standing feature request. Unlike similar proposals, this EIP specifies a method of implementing sponsored transactions that allows both externally owned accounts (EOAs) and EIP-2938 contracts to act as sponsors.
With the explosion of tokens built on Ethereum, especially stable coins, it has become common for EOAs to hold valuable assets without holding any Ether at all. These assets must be converted to Ether before they can be used to pay gas fees, but without Ether to pay for the conversion, it's impossible to convert them. Sponsored transactions break the circular dependency.
While it is possible to emulate sponsored transactions (ex. Gas Station Network), these solutions require specific support in callee contracts.
Specification
A precompile, at address 0x13
, functions like a CALL
instruction that additionally:
- sets the caller and origin addresses based on an ECDSA signature, and
- optionally transfers Ether from the recovered account.
When invoked through CALL
(0xF1
), DELEGATECALL
(0xF4
), or CALLCODE
(0xF2
) the CALL_PRECOMPILE
invokes the callee as if it had used CALL
(0xF1
.) When invoked through STATICCALL
(0xFA
), the CALL_PRECOMPILE
invokes the callee as if it had used STATICCALL
(0xFA
.)
Definitions
CALL_PRECOMPILE
- the specific precompile at address0x13
, introduced by this EIP, which implements theCALL
analogue.- Transaction-like Package - the signed arguments passed to
CALL_PRECOMPILE
. - Sponsor - the account which is responsible for paying gas fees and sending the transaction.
- Sponsee - the account which signed the transaction-like package.
- Invoker - the account or contract which directly calls into
CALL_PRECOMPILE
. - Callee - the target of the call from
CALL_PRECOMPILE
.
Constants
Constant | Value | Description |
---|---|---|
SPONSORED_TYPE |
0x03 |
EIP-2718 transaction type reserved for transaction-like packages. |
API
CALL_PRECOMPILE
CALL_PRECOMPILE
requires the following arguments:
type: uint8
- EIP-2718 transaction type (currently alwaysSPONSORED_TYPE
);sponsee: address
- address of the sponsee;nextra: uint256
- extra data, which can be used in the invoker to implement replay protection;to: address
- address of the callee (notCALL_PRECOMPILE
);mingas: uint256
- minimum gas limit which must be provided with the call intoCALL_PRECOMPILE
;valueTotal: uint256
- exact amount of Ether in wei to be received by the callee;valueSponseeMax: uint256
- maximum amount of Ether in wei that can be deducted from the sponsee's balance;data: bytes
- the calldata for the call intoto
; andv: uint8
,r: bytes32
,s: bytes32
- signature for the package, including chain id as specified in EIP-155.
The arguments to CALL_PRECOMPILE
are encoded as type || abi.encode(sponsee, nextra, mingas, to, valueTotal, valueSponseeMax, data, v, r, s)
.
The signature (v
, r
, s
) arguments are computed from secp256k1(keccak256(type || abi.encode(nextra, mingas, to, valueTotal, valueSponseeMax, data, invoker, chainid)))
.
CALL_PRECOMPILE
returns a failure in the following situations:
type != SPONSORED_TYPE
- Invalid signature
- The address recovered from
v
,r
, ands
does not matchsponsee
- Gas limit supplied with the call into
CALL_PRECOMPILE
is less than the signedmingas
- The transaction's remaining gas is less than the signed
mingas
- The value sent with the call is less than
valueTotal - min(valueSponseeMax, valueTotal)
- The value sent with the call is greater than
valueTotal
- The sponsee's balance is less than
valueTotal
minus the value sent with the call
CALL_PRECOMPILE
returns a success in all other cases.
The return data of CALL_PRECOMPILE
will be a single byte to indicate the status of the call into callee followed immediately by the return data from that call.
Gas Fees
CALL_PRECOMPILE
The gas fees for CALL_PRECOMPILE
are calculated according the the following pseudo-code:
S_cd = len(data) # In 256-bit words, rounded up
fees = 3200 + (12 * S_cd) + (S_cd**2 // 512)
if valueTotal > msg.value and valueSponseeMax != 0:
fees += 400
if preconditions_good(...):
return fees + cost_of_call(S_cd) # TODO
else:
return fees
Rationale
Prefixing Callee Return Data with Status
It is important to differentiate between a failure in CALL_PRECOMPILE
's preconditions versus a failure in the callee. Correctly implementing replay protection requires the invoker to change its state even if the callee fails (to burn the nonce) but doing so if, for example, the signature failed would be nonsensical.
Sponsee in Arguments
Including sponsee
in the arguments to CALL_PRECOMPILE
is a gas optimization. Without it, invokers would have to do their own ecrecover
before calling into CALL_PRECOMPILE
to verify/adjust any state for replay protection.
Sponsoring an Account with Ether
Allowing CALL_PRECOMPILE
to transfer Ether on the sponsee's behalf provides a uniform interface for all transactions a sponsee might want to perform. Sponsees with sufficient Ether might use a sponsor when swapping Ether for an ERC-20 on a subsidized exchange, or when relying on that sponsor to resubmit transactions to optimize gas pricing.
Reserving an EIP-2718 Transaction Type
While clients should never directly interpret transaction-like packages as true transactions, reserving an EIP-2718 transaction type for transaction-like packages reduces the likelihood of a transaction-like package being misinterpreted as a true transaction.
Another Sponsored Transaction EIP
Other approaches to sponsored transactions, which rely on introducing a new transaction type, are not immediately compatible with account abstraction (AA). These proposals require a signed transaction from the sponsor's account, which is not possible from an AA contract, because it has no private key to sign with.
Besides better compatibility with AA, a precompile is a much less intrusive change than a new transaction type. This approach requires no changes in existing wallets, and little change in other tooling.
CALL_PRECOMPILE
's single purpose is to set CALLER
. It implements the minimal functionality to enable sender abstraction for sponsored transactions. This single mindedness makes CALL_PRECOMPILE
significantly more composable with existing Ethereum features.
More logic can be implemented around the call into CALL_PRECOMPILE
, giving more control to invokers and sponsors without sacrificing security or user experience for sponsees.
Lack of Replay Protection
Earlier approaches to this problem included mechanisms for replay protection. This proposal explicitly does not handle replay protection, but instead includes a signed-but-unused field (nextra
) which is expected to be used by invoker contracts to implement replay protection. Delegating replay protection to the invoker sidesteps the issue of giving a precompile contract its own storage, while opening the door to more innovative replay protection methods in the future.
Precompile vs. Opcode
There are several implementation details about this operation that might evolve over time (ex. the signature algorithm.) If each evolution of sponsored transactions required a new opcode, the opcode space would be quickly exhausted.
Further, as a precompile, sponsored transactions can be combined with the existing call opcodes (CALL
, STATICCALL
, etc) to achieve different levels of state mutability. This would require several opcodes (SIGCALL
, SIGSTATICCALL
, etc) and would further exhaust the opcode space.
Changing SENDER
& ORIGIN
Existing contracts were built under assumptions that this proposal changes or outright breaks.
extcodesize(msg.sender) == 0
asserts that the caller is not a contract.
This is not strictly true without this proposal (ex. contracts under construction have no code.) Contracts relying on this assumption are therefore insecure regardless.
tx.origin
is the gas payer.
It is unlikely that contracts making this assumption (i.e. ones which already implement sponsored transactions) would continue to be used after this proposal is implemented.
tx.origin == msg.sender
asserts that the current execution frame is the topmost frame.
Several properties are unique to the topmost execution frame:
- The return value is inaccessible.
- Reverting or returning in the topmost frame ends execution.
- The caller is an EOA and not a contract.
Properties (1) and (2) no longer hold when tx.origin == msg.sender
, while property (3) is maintained: only an EOA can sign a transaction-like package.
It is unlikely, but not impossible, for a contract to only return a value when called by an EOA. It is difficult to imagine uses cases for such behavior, so invalidating property (1) seems to have low impact.
Property (2) would likely have the greatest impact, for two reasons: it creates the opportunity for atomicity where there was none before, and it makes pre- and post-conditions undetectable to the callee. Since the topmost frame is always executed, a contract in that frame can be certain—barring revert and out-of-gas conditions—that it will execute, regardless of the state changes it makes. With CALL_PRECOMPILE
, an invoker could revert the callee if certain post-conditions were not met, allowing a retry at a later time. That said, a miner can break either of these assumptions by, respectively, executing two separate but adjacent transactions, or simply excluding a transaction that doesn't meet the pre- or post-conditions.
Only one
tx.origin
can exist in a single transaction.
Reentrancy guards that rely on tx.origin
cease to function under this proposal.
If setting ORIGIN
is unacceptable, an alternative is to not set ORIGIN
and for CALL_PRECOMPILE
to fail if sponsor == sponsee
.
Backwards Compatibility
No known issues.
Test Cases
TODO
Implementation
TODO
Security Considerations
Changing SENDER
& ORIGIN
tx.origin
is not always the gas payer.- Checking
msg.sender == tx.origin
does not prevent reentrancy. - Contracts cannot prevent or detect pre- or post- conditions by checking
msg.sender == tx.origin
. - Return values which were only accessible to EOAs are accessible to invoker contracts.
Signature Verification & Reply Protection
- Potential impersonation attacks if there is a bug in the signature verification.
- Replay protection can be poorly implemented (or even maliciously broken) in the invoker.
Frontrunning
- Transaction-like packages can be extracted from the original sponsor's transaction and resent by another sponsor.
Copyright
Copyright and related rights waived via CC0.