EIPs/EIPS/eip-3074.md
Sam Wilson fbae2549c7
Automatically merged updates to draft EIP(s) 3074 (#3344)
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
2021-03-08 19:26:59 +13:00

11 KiB

eip title author discussions-to status type category created
3074 Native Sponsored Transactions 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 EVM instruction, analogous to CALL (0xF1), that sets CALLER (0x33) based on an ECDSA signature.

Abstract

This EIP creates an EVM instruction (NAMETBD) 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 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

An opcode, at 0xf9, shall function like a CALL (0xF1) instruction:

  • To the to address,
  • Transferring value wei from the invoker to the callee,
  • Passing gas gas for execution,
  • With calldata in the memory region specified by argsOffset and argsLength,
  • With a return data region specified by retOffset and retLength.

Additionally, the opcode shall:

  • Set the caller address based on an ECDSA signature.

NAMETBD shall increase the call depth by one, in the same way as CALL. NAMETBD shall not increase the call depth by two (as it would if it first called into the sponsee account and then into the callee.)

In a static context (such as the one created by STATICCALL), NAMETBD with a non-zero value shall exit the current execution frame immediately (in the same way CALL behaves with a non-zero value in a static context.)

Definitions

  • NAMETBD - the specific instruction encoded as 0xf9, introduced by this EIP, which implements the CALL analogue.
  • Transaction-like Package - the signed arguments passed to NAMETBD.
  • 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 NAMETBD.
  • Callee - the target of the call from NAMETBD.

Conventions

  • top - N - the Nth most recently pushed value on the EVM stack, where top - 0 is the most recent.
  • || - byte concatenation operator.

Constants

Constant Value Description
SPONSORED_TYPE 0x03 EIP-2718 transaction type reserved for transaction-like packages.

API

Inputs

NAMETBD shall require the following stack arguments:

Stack Value
top - 0 yParity
top - 1 r
top - 2 s
top - 3 sponsee
top - 4 type
top - 5 nextra
top - 6 gas
top - 7 callee
top - 8 value
top - 9 argsOffset
top - 10 argsLength
top - 11 retOffset
top - 12 retLength

The signature (yParity, r, s) arguments shall be computed from secp256k1(keccak256(type || abi.encode(invoker, chainid, nextra, gas, callee, value, data))).

The arguments are defined to be:

  • type: uint8 - EIP-2718 transaction type (currently always SPONSORED_TYPE);
  • invoker: address - the address of the invoker contract;
  • chainid: uint256 - the chain id, as returned by the CHAINID (0x46) opcode;
  • nextra: uint256 - extra data, which can be used in the invoker to implement replay protection;
  • callee: address - address of the callee;
  • gas: uint256 - exact gas limit which must be provided with the call into NAMETBD;
  • value: uint256 - exact amount of Ether in wei to be received by the callee;
  • data: bytes - the calldata for the call into the callee;
  • sponsee: address - address of the sponsee;
  • argsOffset: uint256, argsLength: uint256 - region of memory used as the calldata for the call into the callee, which should be equal to data;
  • retOffset: uint256, retLength: uint256 - region of memory filled with the return data from the call into the callee; and
  • yParity: uint8, r: bytes32, s: bytes32 - signature for the package.

Outputs

NAMETBD pushes the following two values onto the stack:

Stack Value
top - 0 valid
top - 1 success
valid

valid shall be zero in the following cases:

  • type != SPONSORED_TYPE
  • Invalid signature
  • The address recovered from yParity, r, and s does not match sponsee
  • Gas limit supplied with the call into NAMETBD is not equal to the signed gas
  • The transaction's remaining gas is less than the signed gas
  • The value sent with the call is not equal to the signed value

valid shall be a one in all other cases.

success

success 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

success 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 NAMETBD are calculated according to the following pseudo-code:

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 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 NAMETBD's preconditions versus a failure in the callee. Correctly implementing replay protection requires the invoker to change its state (i.e. burn the nonce) even if the callee fails; 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 NAMETBD 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 NAMETBD pre-conditions were invalidated, which implies reading and comparing memory values. Further, to remain sound if a callee were to revert with NAMETBD failed, NAMETBD 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 NAMETBD is a gas optimization for invoker contracts implementing some replay protection based on the sponsee address. Without the sponsee argument, invokers would have to do their own ecrecover before calling into NAMETBD to verify/adjust any state for replay protection.

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, 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.

NAMETBD's single purpose is to set CALLER. It implements the minimal functionality to enable sender abstraction for sponsored transactions. This single mindedness makes NAMETBD significantly more composable with existing Ethereum features.

More logic can be implemented around the NAMETBD instruction, 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 and related rights waived via CC0.