--- eip: 3074 title: Native Sponsored Transactions author: Sam Wilson (@SamWilsn) discussions-to: https://ethereum-magicians.org/t/eip-3074-sponsored-transaction-precompile/4880 status: Draft type: Standards Track category: Core created: 2020-10-15 --- ## Simple Summary Creates a new EVM instruction, analogous to `CALL` (`0xF1`), that sets `CALLER` (`0x33`) based on an ECDSA signature. ## Abstract This EIP creates an EVM instruction (`TXCALL`) which forwards a `CALL`, setting `CALLER` 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](./eip-2938.md) 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](https://www.opengsn.org/)), these solutions require specific support in callee contracts. ## Specification An opcode, at `0xf9`, functions like a `CALL` instruction that additionally: - sets the caller addresses based on an ECDSA signature, and - optionally transfers Ether from the recovered account. ### Definitions - **`TXCALL`** - the specific instruction encoded as `0xf9`, introduced by this EIP, which implements the `CALL` analogue. - **Transaction-like Package** - the signed arguments passed to `TXCALL`. - **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 contains `TXCALL`. - **Callee** - the target of the call from `TXCALL`. ### Constants | Constant | Value | Description | | ---------------- | ------ |:----------------------------------------------------------------------------------- | | `SPONSORED_TYPE` | `0x03` | [EIP-2718](./eip-2718.md) transaction type reserved for transaction-like packages. | ### API #### Inputs `TXCALL` requires the following stack arguments: | `top - 0` | `top - 1` | `top - 2` | `top - 3` | `top - 4` | `top - 5` | | --------- | --------- | ------------ | ------------ | ----------- | ----------- | | `gas` | `value` | `argsOffset` | `argsLength` | `retOffset` | `retLength` | The arguments memory region shall be encoded as `type || abi.encode(v, r, s, sponsee, nextra, mingas, to, data)`. The signature (`v`, `r`, `s`) arguments are computed from `secp256k1(keccak256(type || abi.encode(invoker, chainid, value, nextra, mingas, to, data)))`. The arguments are: - `type: uint8` - [EIP-2718](./eip-2718.md) transaction type (currently always `SPONSORED_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; - `mingas: uint256` - minimum gas limit which must be provided with the call into `TXCALL`; - `value: uint256` - exact amount of Ether in wei to be received by the callee; - `data: bytes` - the calldata for the call into `to`; and - `v: uint8`, `r: bytes32`, `s: bytes32` - signature for the package, including chain id as specified in [EIP-155](./eip-155.md). #### Outputs `TXCALL` pushes the following two values onto the stack: | `top - 0` | `top - 1` | | ------------- | ----------------- | | `success` | `calleeSuccess` | ##### `success` `success` shall be zero in the following cases: - `type != SPONSORED_TYPE` - Invalid signature - The address recovered from `v`, `r`, and `s` does not match `sponsee` - Gas limit supplied with the call into `TXCALL` is less than the signed `mingas` - The transaction's remaining gas is less than the signed `mingas` - The value sent with the call is not equal to the signed `value` - The current execution context is static (i.e. `STATICCALL`) and `value` is non-zero `success` shall be a one in all other cases. ##### `calleeSuccess` `calleeSuccess` shall be zero in the following cases: - `success == 0` - the code execution failed due to an exceptional halting or revert - call depth limit has been reached `calleeSuccess` shall be a one in all other cases. ##### Return Data The memory region defined by `retOffset` and `retLength` shall be filled in the same way as the `CALL` instruction with similar arguments. The return data area accessed with `RETURNDATASIZE` (`0x3d`) and `RETURNDATACOPY` (`0x3e`) shall be set in the same way as the `CALL` instruction. ### Gas Fees The gas fees for `TXCALL` are calculated according to the following pseudo-code: ```python S_cd = len(data) # In 256-bit words, rounded up fees = 3200 + (6 * S_cd) if preconditions_good(...): return fees + cost_of_call(...) else: return fees ``` Where `len(data)` is the length of the region of memory defined by `argsOffset` and `argsLength`, rounded up to the nearest 256-bit word, and `cost_of_call(...)` is the cost of a `CALL` (`0xF1`) instruction with the same `gas`, `value`, `argsOffset`, `argsLength`, `retOffset`, and `retLength` arguments. ## Rationale ### Omitting Arguments The signature arguments `value`, `chainid`, and `invoker` are not included in the memory region arguments to the instruction because they can be calculated by the instruction itself. ### Two Return Values It is important to differentiate between a failure in `TXCALL`'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. Several options exist for encoding these two failure cases: returning two stack elements, reserving a specific revert reason, or choosing different values in a single stack element. First, it's important to note that all three options are a deviation from the semantics of other `CALL` opcodes, but this deviation is unavoidable. Reserving a specific revert reason, for example `txcall failed`, is a large departure from other instructions. An invoker would need to inspect the revert reason to determine whether the callee reverted, or the `TXCALL` pre-conditions were invalidated, which implies reading and comparing memory values. Further, to remain sound when a callee reverts with `txcall failed`, `TXCALL` would need to replace the return data with some other value. Returning a single stack element with different values depending on the situation (ex. `0` on success, `1` when the pre-conditions are violated, and `2` when the callee reverts) introduces the opportunity for a subtle bug: it's trivially easy to misinterpret the return value (`CALL` returns non-zero on success), but it's much harder to ignore a whole new stack value. ### Sponsee in Arguments Including `sponsee` in the arguments to `TXCALL` is a gas optimization. Without it, invokers would have to do their own `ecrecover` before calling into `TXCALL` to verify/adjust any state for replay protection. ### Reserving an [EIP-2718](./eip-2718.md) Transaction Type While clients should never directly interpret transaction-like packages as true transactions, reserving an [EIP-2718](./eip-2718.md) 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, an instruction 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. `TXCALL`'s single purpose is to set `CALLER`. It implements the minimal functionality to enable sender abstraction for sponsored transactions. This single mindedness makes `TXCALL` significantly more composable with existing Ethereum features. More logic can be implemented around the call into `TXCALL`, 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. ## Backwards Compatibility No known issues. ## Test Cases TODO ## Implementation TODO ## Security Considerations ### Reentrancy - Checking `msg.sender == tx.origin` no longer prevents reentrancy. _Adding the pre-condition that `sponsor != sponsee` would restore this property._ ### 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](https://creativecommons.org/publicdomain/zero/1.0/).