From bfeb6d7c70b4f6ef2a8904278d9f7926a4936b94 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Fri, 12 Mar 2021 15:07:35 +0100 Subject: [PATCH 01/11] [EIP-3074] Update specification with two-opcode split --- EIPS/eip-3074.md | 160 ++++++++++++++++++++--------------------------- 1 file changed, 69 insertions(+), 91 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index 28244b60cf9ca1..38ab5e7ec108c8 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -1,6 +1,6 @@ --- eip: 3074 -title: Native Sponsored Transactions +title: AUTHORIZE and AUTHORIZEDCALL opcodes author: Sam Wilson (@SamWilsn), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient), Micah Zoltu (@micahzoltu) discussions-to: https://ethereum-magicians.org/t/eip-3074-sponsored-transaction-precompile/4880 status: Draft @@ -11,11 +11,11 @@ created: 2020-10-15 ## Simple Summary -Creates a new EVM instruction, analogous to `CALL` (`0xF1`), that sets `CALLER` (`0x33`) based on an ECDSA signature. +Creates two new EVM instructions that allow an externally owned account (EOA) to authorize a specific contract via an ECDSA signature to send calls on it's behalf. ## Abstract -This EIP creates an EVM instruction (`CALLFROM`) which forwards a `CALL`, setting `CALLER` according to an ECDSA signature. +This EIP creates two new EVM instruction. The first, `AUTHORIZE`, sets a context variable `authorizedAccount` based on an ECDSA signature. The second, `AUTHORIZEDCALL`, sends a call on behalf of `authorizedAccount`, setting the caller address to and deducting the call's value from the authorized account. ## Motivation @@ -27,125 +27,103 @@ While it is possible to emulate sponsored transactions (ex. [Gas Station Network ## Specification -An opcode, at `0xf9`, must 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`. +### Conventions -Additionally, the opcode must: + - **`top - N`** - the `N`th most recently pushed value on the EVM stack, where `top - 0` is the most recent. + - **`||`** - byte concatenation operator. - - Set the caller address based on an ECDSA signature. +### Constants -`CALLFROM` must increase the call depth by one, in the same way as `CALL`. `CALLFROM` must not increase the call depth by two (as it would if it first called into the sponsee account and then into the callee.) +| Constant | Value | +| ---------------- | ------ | +| `TYPE` | `0x03` | -In a static context (such as the one created by `STATICCALL`), `CALLFROM` with a non-zero `value` must exit the current execution frame immediately (in the same way `CALL` behaves with a non-zero value in a static context.) +`TYPE` is an [EIP-2718](./eip-2718.md) transaction type reserved for EIP-3074 signatures to prevent signature collisions. -### Definitions +### Context Variables - - **`CALLFROM`** - the specific instruction encoded as `0xf9`, introduced by this EIP, which implements the `CALL` analogue. - - **Transaction-like Package (TLP)** - the signed arguments passed to `CALLFROM`. - - **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 `CALLFROM`. - - **Callee** - the target of the call from `CALLFROM`. +| Variable | Type | Initial Value | +| ------------------- | --------- |:------------------------------------------------------------------------------------ | +| `authorizedAccount` | `address` | `0x0000000000000000000000000000000000000000` | -### Conventions +`authorizedAccount` indicates the address of an account the executing contract is authorized to send calls from. The zero address indicates that no such account is set. The variable has the same scope as the program counter: It persists throughout execution of the contract, but is not passed along any calls (including `DELEGATECALL`). - - **`top - N`** - the `N`th most recently pushed value on the EVM stack, where `top - 0` is the most recent. - - **`||`** - byte concatenation operator. +### `AUTHORIZE` (`0xf8`) -### Constants +A new opcode `AUTHORIZE` is created at `0xf8`. It takes in 4 stack elements and returns 1 stack element. -| Constant | Value | Description | -| ---------------- | ------ |:----------------------------------------------------------------------------------- | -| `SPONSORED_TYPE` | `0x03` | [EIP-2718](./eip-2718.md) transaction type reserved for transaction-like packages. | +#### Input -### API +| Stack | Value | +| ---------- | ------------ | +| `top - 0` | `commit` | +| `top - 1` | `yParity` | +| `top - 2` | `r` | +| `top - 3` | `s` | -#### Inputs +#### Output -`CALLFROM` must require the following stack arguments: +| Stack | Value | +| ---------- | ------------------- | +| `top - 0` | `authorizedAccount` | -| Stack | Value | -| ---------- | ------------ | -| `top - 0` | `yParity` | -| `top - 1` | `r` | -| `top - 2` | `s` | -| `top - 3` | `sponsee` | -| `top - 4` | `extra` | -| `top - 5` | `gas` | -| `top - 6` | `callee` | -| `top - 7` | `value` | -| `top - 8` | `argsOffset` | -| `top - 9` | `argsLength` | -| `top - 10` | `retOffset` | -| `top - 11` | `retLength` | - -The signature (`yParity`, `r`, `s`) arguments must be computed from `secp256k1(keccak256(type || abi.encode(invoker, extra)))`. - -The arguments are defined to be: - - - `type: uint8` - [EIP-2718](./eip-2718.md) transaction type (always `SPONSORED_TYPE`); - - `invoker: address` - the address of the invoker contract; - - `extra: 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 `CALLFROM`; - - `value: uint256` - exact amount of Ether in wei to be received by 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; - - `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 - -`CALLFROM` pushes the following two values onto the stack: +#### Behavior -| Stack | Value | -| ---------- | --------- | -| `top - 0` | `valid` | -| `top - 1` | `success` | +The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature of the form `secp256k1(keccak256(TYPE || abi.encode(invoker, commit)))`, where: + - `invoker` is the address of the contract executing `AUTHORIZE`. + - `commit` is one of the arguments passed into `AUTHORIZE` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic. + +If the signature is valid, the `signerAddress` is recovered. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. +In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to the zero address. + +`AUTHORIZE` returns the stack element `authorizedAccount`, which is set to the context variable of the same name. +#### Gas Cost -##### Output: `valid` +The gas cost for `AUTHORIZE` is `3000`, which is the same cost as for the existing ecrecover precompile. -`valid` must be zero in the following cases: - - Invalid signature - - The address recovered from `yParity`, `r`, and `s` does not match `sponsee` - - The balance of the invoker is less than `value` +### `AUTHORIZEDCALL` (`0xf9`) -`valid` must be a one in all other cases. +A new opcode `AUTHORIZEDCALL` is created at `0xf9`. It takes in 7 stack elements and returns 1 stack element. It closely mirrors the behavior of the existing `CALL` (`0xF1`) instruction. -##### Output: `success` +#### Input + +| Stack | Value | +| --------- | ------------ | +| `top - 0` | `gas` | +| `top - 1` | `callee` | +| `top - 2` | `value` | +| `top - 3` | `argsOffset` | +| `top - 4` | `argsLength` | +| `top - 5` | `retOffset` | +| `top - 6` | `retLength` | + +#### Output + +| Stack | Value | +| ---------- | --------- | +| `top - 0` | `success` | -`success` must 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 +#### Behavior -`success` must be a one in all other cases. +`AUTHORIZEDCALL` has the same execution logic as `CALL`, except for: -##### Output: Return Data + - If `authorizedAccount` is the zero address, no call is made and `success` is set to `0`. Otherwise, the caller address for the call is set to `authorizedAccount`. + - The call value is deducted from the `authorizedAccount` balance. If that balance is insufficient, no call is made and `success` is set to `0`. -The memory region defined by `retOffset` and `retLength` must be filled in the same way as the `CALL` instruction with similar arguments. +`AUTHORIZEDCALL` must increase the call depth by one, in the same way as `CALL`. `AUTHORIZEDCALL` must not increase the call depth by two (as it would if it first called into the authorized account and then into the callee.) The return data area accessed with `RETURNDATASIZE` (`0x3d`) and `RETURNDATACOPY` (`0x3e`) must be set in the same way as the `CALL` instruction. -### Gas Fees +In a static context (such as the one created by `STATICCALL`), `AUTHORIZEDCALL` with a non-zero `value` must exit the current execution frame immediately (in the same way `CALL` behaves with a non-zero value in a static context.) -The gas fees for `CALLFROM` are calculated according to the following pseudo-code: +#### Gas Cost -```python -fee = 3200 +`AUTHORIZEDCALL` has the same gas cost as `CALL`, except for: -if preconditions_good(...): - return fee + cost_of_call(...) -else: - return fee -``` + - The dynamic gas cost for non-zero call value is increased from `9000` to `21000`. -Where `cost_of_call(...)` is the cost of a `CALL` (`0xF1`) instruction with the same `gas`, `value`, `argsOffset`, `argsLength`, `retOffset`, and `retLength` arguments. +As with `CALL`, the gas cost for the opcode itself (both the static and the dynamic portion) is always charged, independent of whether the call is actually executed. Also as with `CALL`, the gas passed into the call is calculated following EIP-150 and is refunded partially if the call returns with unused gas left, or completely if the call is not executed at all because of a failing pre-check. This also applies to the modified pre-check for insufficient balance and the newly added pre-check for a zero `authorizedAccount` variable. ## Rationale From 83a900e1b0f75b87b9566dcb2c9e5f8297d14966 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Fri, 12 Mar 2021 23:13:59 +0100 Subject: [PATCH 02/11] Apply Matt's suggestions Co-authored-by: lightclient <14004106+lightclient@users.noreply.github.com> --- EIPS/eip-3074.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index 38ab5e7ec108c8..f37db5fd493689 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -15,7 +15,7 @@ Creates two new EVM instructions that allow an externally owned account (EOA) to ## Abstract -This EIP creates two new EVM instruction. The first, `AUTHORIZE`, sets a context variable `authorizedAccount` based on an ECDSA signature. The second, `AUTHORIZEDCALL`, sends a call on behalf of `authorizedAccount`, setting the caller address to and deducting the call's value from the authorized account. +This EIP introduces two EVM instruction `AUTHORIZE` and `AUTHORIZEDCALL`. The first sets a context variable `authorizedAccount` based on an ECDSA signature. The second sends a call as the `authorizedAccount`. ## Motivation @@ -46,7 +46,7 @@ While it is possible to emulate sponsored transactions (ex. [Gas Station Network | ------------------- | --------- |:------------------------------------------------------------------------------------ | | `authorizedAccount` | `address` | `0x0000000000000000000000000000000000000000` | -`authorizedAccount` indicates the address of an account the executing contract is authorized to send calls from. The zero address indicates that no such account is set. The variable has the same scope as the program counter: It persists throughout execution of the contract, but is not passed along any calls (including `DELEGATECALL`). +`authorizedAccount` indicates the address of an account the executing contract is authorized to send calls from. The zero address indicates that no such account is set. The variable has the same scope as the program counter -- it persists throughout execution of the contract, but is not passed along any calls (including `DELEGATECALL`). ### `AUTHORIZE` (`0xf8`) @@ -76,22 +76,22 @@ The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature of the If the signature is valid, the `signerAddress` is recovered. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to the zero address. -`AUTHORIZE` returns the stack element `authorizedAccount`, which is set to the context variable of the same name. +`AUTHORIZE` returns the stack element `authorizedAccount`, which is set to the recovered address. #### Gas Cost -The gas cost for `AUTHORIZE` is `3000`, which is the same cost as for the existing ecrecover precompile. +The gas cost for `AUTHORIZE` is `3000`. This is the same cost as for the `ecrecover` precompile. ### `AUTHORIZEDCALL` (`0xf9`) -A new opcode `AUTHORIZEDCALL` is created at `0xf9`. It takes in 7 stack elements and returns 1 stack element. It closely mirrors the behavior of the existing `CALL` (`0xF1`) instruction. +A new opcode `AUTHORIZEDCALL` is created at `0xf9`. It takes in 7 stack elements and returns 1 stack element. It matches the behavior of the existing `CALL` (`0xF1`) instruction, except where noted below. #### Input | Stack | Value | | --------- | ------------ | | `top - 0` | `gas` | -| `top - 1` | `callee` | +| `top - 1` | `addr` | | `top - 2` | `value` | | `top - 3` | `argsOffset` | | `top - 4` | `argsLength` | @@ -106,12 +106,12 @@ A new opcode `AUTHORIZEDCALL` is created at `0xf9`. It takes in 7 stack elements #### Behavior -`AUTHORIZEDCALL` has the same execution logic as `CALL`, except for: +`AUTHORIZEDCALL` is interpreted the same as `CALL`, except for: - If `authorizedAccount` is the zero address, no call is made and `success` is set to `0`. Otherwise, the caller address for the call is set to `authorizedAccount`. - The call value is deducted from the `authorizedAccount` balance. If that balance is insufficient, no call is made and `success` is set to `0`. -`AUTHORIZEDCALL` must increase the call depth by one, in the same way as `CALL`. `AUTHORIZEDCALL` must not increase the call depth by two (as it would if it first called into the authorized account and then into the callee.) +`AUTHORIZEDCALL` must increase the call depth by one. `AUTHORIZEDCALL` must not increase the call depth by two as if it first called into the authorized account and then into the target. The return data area accessed with `RETURNDATASIZE` (`0x3d`) and `RETURNDATACOPY` (`0x3e`) must be set in the same way as the `CALL` instruction. From efb9c9b1214d84dbcbc1365e4a3d3ae2a35e371e Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Fri, 12 Mar 2021 23:24:05 +0100 Subject: [PATCH 03/11] [EIP-3074] Change opcode names and bytes --- EIPS/eip-3074.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index f37db5fd493689..cc77b5a1be8703 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -1,6 +1,6 @@ --- eip: 3074 -title: AUTHORIZE and AUTHORIZEDCALL opcodes +title: AUTH and AUTHCALL opcodes author: Sam Wilson (@SamWilsn), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient), Micah Zoltu (@micahzoltu) discussions-to: https://ethereum-magicians.org/t/eip-3074-sponsored-transaction-precompile/4880 status: Draft @@ -15,7 +15,7 @@ Creates two new EVM instructions that allow an externally owned account (EOA) to ## Abstract -This EIP introduces two EVM instruction `AUTHORIZE` and `AUTHORIZEDCALL`. The first sets a context variable `authorizedAccount` based on an ECDSA signature. The second sends a call as the `authorizedAccount`. +This EIP introduces two EVM instruction `AUTH` and `AUTHCALL`. The first sets a context variable `authorizedAccount` based on an ECDSA signature. The second sends a call as the `authorizedAccount`. ## Motivation @@ -48,9 +48,9 @@ While it is possible to emulate sponsored transactions (ex. [Gas Station Network `authorizedAccount` indicates the address of an account the executing contract is authorized to send calls from. The zero address indicates that no such account is set. The variable has the same scope as the program counter -- it persists throughout execution of the contract, but is not passed along any calls (including `DELEGATECALL`). -### `AUTHORIZE` (`0xf8`) +### `AUTH` (`0xf6`) -A new opcode `AUTHORIZE` is created at `0xf8`. It takes in 4 stack elements and returns 1 stack element. +A new opcode `AUTH` is created at `0xf6`. It takes in 4 stack elements and returns 1 stack element. #### Input @@ -70,21 +70,21 @@ A new opcode `AUTHORIZE` is created at `0xf8`. It takes in 4 stack elements and #### Behavior The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature of the form `secp256k1(keccak256(TYPE || abi.encode(invoker, commit)))`, where: - - `invoker` is the address of the contract executing `AUTHORIZE`. - - `commit` is one of the arguments passed into `AUTHORIZE` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic. + - `invoker` is the address of the contract executing `AUTH`. + - `commit` is one of the arguments passed into `AUTH` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic. If the signature is valid, the `signerAddress` is recovered. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to the zero address. -`AUTHORIZE` returns the stack element `authorizedAccount`, which is set to the recovered address. +`AUTH` returns the stack element `authorizedAccount`, which is set to the recovered address. #### Gas Cost -The gas cost for `AUTHORIZE` is `3000`. This is the same cost as for the `ecrecover` precompile. +The gas cost for `AUTH` is `3000`. This is the same cost as for the `ecrecover` precompile. -### `AUTHORIZEDCALL` (`0xf9`) +### `AUTHCALL` (`0xf7`) -A new opcode `AUTHORIZEDCALL` is created at `0xf9`. It takes in 7 stack elements and returns 1 stack element. It matches the behavior of the existing `CALL` (`0xF1`) instruction, except where noted below. +A new opcode `AUTHCALL` is created at `0xf7`. It takes in 7 stack elements and returns 1 stack element. It matches the behavior of the existing `CALL` (`0xF1`) instruction, except where noted below. #### Input @@ -106,20 +106,20 @@ A new opcode `AUTHORIZEDCALL` is created at `0xf9`. It takes in 7 stack elements #### Behavior -`AUTHORIZEDCALL` is interpreted the same as `CALL`, except for: +`AUTHCALL` is interpreted the same as `CALL`, except for: - If `authorizedAccount` is the zero address, no call is made and `success` is set to `0`. Otherwise, the caller address for the call is set to `authorizedAccount`. - The call value is deducted from the `authorizedAccount` balance. If that balance is insufficient, no call is made and `success` is set to `0`. -`AUTHORIZEDCALL` must increase the call depth by one. `AUTHORIZEDCALL` must not increase the call depth by two as if it first called into the authorized account and then into the target. +`AUTHCALL` must increase the call depth by one. `AUTHCALL` must not increase the call depth by two as if it first called into the authorized account and then into the target. The return data area accessed with `RETURNDATASIZE` (`0x3d`) and `RETURNDATACOPY` (`0x3e`) must be set in the same way as the `CALL` instruction. -In a static context (such as the one created by `STATICCALL`), `AUTHORIZEDCALL` with a non-zero `value` must exit the current execution frame immediately (in the same way `CALL` behaves with a non-zero value in a static context.) +In a static context (such as the one created by `STATICCALL`), `AUTHCALL` with a non-zero `value` must exit the current execution frame immediately (in the same way `CALL` behaves with a non-zero value in a static context.) #### Gas Cost -`AUTHORIZEDCALL` has the same gas cost as `CALL`, except for: +`AUTHCALL` has the same gas cost as `CALL`, except for: - The dynamic gas cost for non-zero call value is increased from `9000` to `21000`. From f7f00afc1a3d0f41de105ab6f337adbc9ea6285b Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Fri, 12 Mar 2021 23:43:29 +0100 Subject: [PATCH 04/11] [EIP-3074] Take call value from invoker balance --- EIPS/eip-3074.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index cc77b5a1be8703..c3fee3f2c09c7c 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -15,7 +15,7 @@ Creates two new EVM instructions that allow an externally owned account (EOA) to ## Abstract -This EIP introduces two EVM instruction `AUTH` and `AUTHCALL`. The first sets a context variable `authorizedAccount` based on an ECDSA signature. The second sends a call as the `authorizedAccount`. +This EIP introduces two EVM instructions `AUTH` and `AUTHCALL`. The first sets a context variable `authorizedAccount` based on an ECDSA signature. The second sends a call as the `authorizedAccount`. ## Motivation @@ -69,14 +69,14 @@ A new opcode `AUTH` is created at `0xf6`. It takes in 4 stack elements and retur #### Behavior -The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature of the form `secp256k1(keccak256(TYPE || abi.encode(invoker, commit)))`, where: - - `invoker` is the address of the contract executing `AUTH`. - - `commit` is one of the arguments passed into `AUTH` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic. +The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature on the secp256k1 curve over the message `keccak256(TYPE || paddedInvokerAddress || commit)`, where: + - `paddedInvokerAddress` is the 32-byte right-alligned address of the contract executing `AUTH`, padded with zero-bytes. + - `commit` is one of the arguments passed into `AUTH` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic (e.g. a nonce for replay protection). If the signature is valid, the `signerAddress` is recovered. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to the zero address. -`AUTH` returns the stack element `authorizedAccount`, which is set to the recovered address. +`AUTH` returns the newly set `authorizedAccount` variable as a stack element. #### Gas Cost @@ -108,22 +108,22 @@ A new opcode `AUTHCALL` is created at `0xf7`. It takes in 7 stack elements and r `AUTHCALL` is interpreted the same as `CALL`, except for: - - If `authorizedAccount` is the zero address, no call is made and `success` is set to `0`. Otherwise, the caller address for the call is set to `authorizedAccount`. - - The call value is deducted from the `authorizedAccount` balance. If that balance is insufficient, no call is made and `success` is set to `0`. + - If `authorizedAccount` is the zero address, no call is made and `success` is set to `0`. + - Otherwise, the caller address for the call is set to `authorizedAccount`. + +The call value is deducted from the balance of the executing contract. It is not paid by the `authorizedAccount`. `AUTHCALL` must increase the call depth by one. `AUTHCALL` must not increase the call depth by two as if it first called into the authorized account and then into the target. The return data area accessed with `RETURNDATASIZE` (`0x3d`) and `RETURNDATACOPY` (`0x3e`) must be set in the same way as the `CALL` instruction. -In a static context (such as the one created by `STATICCALL`), `AUTHCALL` with a non-zero `value` must exit the current execution frame immediately (in the same way `CALL` behaves with a non-zero value in a static context.) +Importantly, `AUTHCALL` does not reset `authorizedAccount`, but leaves it unchanged. #### Gas Cost -`AUTHCALL` has the same gas cost as `CALL`, except for: - - - The dynamic gas cost for non-zero call value is increased from `9000` to `21000`. +`AUTHCALL` has the same gas cost as `CALL`. -As with `CALL`, the gas cost for the opcode itself (both the static and the dynamic portion) is always charged, independent of whether the call is actually executed. Also as with `CALL`, the gas passed into the call is calculated following EIP-150 and is refunded partially if the call returns with unused gas left, or completely if the call is not executed at all because of a failing pre-check. This also applies to the modified pre-check for insufficient balance and the newly added pre-check for a zero `authorizedAccount` variable. +As with `CALL`, the gas cost for the opcode itself (both the static and the dynamic portion) is always charged, independent of whether the call is actually executed. The gas passed into the call is calculated following EIP-150 and is refunded partially if the call returns with unused gas left, or completely if the call is not executed at all because of a failing pre-check. This also applies to the newly added pre-check for a zero `authorizedAccount` variable. ## Rationale From 79101fe9f757478789963079f7168215bc50069a Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Fri, 12 Mar 2021 23:57:12 +0100 Subject: [PATCH 05/11] [EIP-3074] Clarify signature validity --- EIPS/eip-3074.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index c3fee3f2c09c7c..3d74c50196abe7 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -73,7 +73,7 @@ The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature on the - `paddedInvokerAddress` is the 32-byte right-alligned address of the contract executing `AUTH`, padded with zero-bytes. - `commit` is one of the arguments passed into `AUTH` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic (e.g. a nonce for replay protection). -If the signature is valid, the `signerAddress` is recovered. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. +If the signature is valid, the `signerAddress` is recovered. Signature validity and signer recovery is handled analogous to transaction signatures, including the stricter `s` range for preventing ECDSA malleability. Note that `yParity` is expected to be `0` or `1`. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to the zero address. `AUTH` returns the newly set `authorizedAccount` variable as a stack element. From 0b9ab65376b304c45340984fdb023a7155f976a8 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Sat, 13 Mar 2021 00:03:30 +0100 Subject: [PATCH 06/11] [EIP-3074] Use unset instead of zero address --- EIPS/eip-3074.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index 3d74c50196abe7..2c9a15c003c073 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -42,11 +42,11 @@ While it is possible to emulate sponsored transactions (ex. [Gas Station Network ### Context Variables -| Variable | Type | Initial Value | -| ------------------- | --------- |:------------------------------------------------------------------------------------ | -| `authorizedAccount` | `address` | `0x0000000000000000000000000000000000000000` | +| Variable | Type | Initial Value | +| ------------------- | --------- |:------------- | +| `authorizedAccount` | `address` | unset | -`authorizedAccount` indicates the address of an account the executing contract is authorized to send calls from. The zero address indicates that no such account is set. The variable has the same scope as the program counter -- it persists throughout execution of the contract, but is not passed along any calls (including `DELEGATECALL`). +`authorizedAccount` indicates the address of an account the executing contract is authorized to send calls from. An unset value indicates that no such account is set. The variable has the same scope as the program counter -- it persists throughout execution of the contract, but is not passed along any calls (including `DELEGATECALL`). ### `AUTH` (`0xf6`) @@ -74,7 +74,7 @@ The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature on the - `commit` is one of the arguments passed into `AUTH` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic (e.g. a nonce for replay protection). If the signature is valid, the `signerAddress` is recovered. Signature validity and signer recovery is handled analogous to transaction signatures, including the stricter `s` range for preventing ECDSA malleability. Note that `yParity` is expected to be `0` or `1`. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. -In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to the zero address. +In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to an unset value. `AUTH` returns the newly set `authorizedAccount` variable as a stack element. @@ -108,7 +108,7 @@ A new opcode `AUTHCALL` is created at `0xf7`. It takes in 7 stack elements and r `AUTHCALL` is interpreted the same as `CALL`, except for: - - If `authorizedAccount` is the zero address, no call is made and `success` is set to `0`. + - If `authorizedAccount` is unset, no call is made and `success` is set to `0`. - Otherwise, the caller address for the call is set to `authorizedAccount`. The call value is deducted from the balance of the executing contract. It is not paid by the `authorizedAccount`. From 5ebcde63f22065905237b5d725a3e8a6d9009af4 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Sat, 13 Mar 2021 00:10:19 +0100 Subject: [PATCH 07/11] [EIP-3074] Clarify AUTH return value --- EIPS/eip-3074.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index 2c9a15c003c073..699336fa298c38 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -76,7 +76,7 @@ The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature on the If the signature is valid, the `signerAddress` is recovered. Signature validity and signer recovery is handled analogous to transaction signatures, including the stricter `s` range for preventing ECDSA malleability. Note that `yParity` is expected to be `0` or `1`. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to an unset value. -`AUTH` returns the newly set `authorizedAccount` variable as a stack element. +`AUTH` returns the new `authorizedAccount` if set, or `0` otherwise. #### Gas Cost From 094fb495e6d583a11b728370e2a9a938cb671f09 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Sat, 13 Mar 2021 00:30:26 +0100 Subject: [PATCH 08/11] [EIP-3074] Modify AUTHCALL to throw on unset authorizedAccount --- EIPS/eip-3074.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index 699336fa298c38..c362c2c8a58dce 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -108,7 +108,7 @@ A new opcode `AUTHCALL` is created at `0xf7`. It takes in 7 stack elements and r `AUTHCALL` is interpreted the same as `CALL`, except for: - - If `authorizedAccount` is unset, no call is made and `success` is set to `0`. + - If `authorizedAccount` is unset, execution is considered invalid and must exit the current execution frame immediately (in the same way it would do e.g. for a stack underflow or an invalid jump). - Otherwise, the caller address for the call is set to `authorizedAccount`. The call value is deducted from the balance of the executing contract. It is not paid by the `authorizedAccount`. @@ -123,7 +123,7 @@ Importantly, `AUTHCALL` does not reset `authorizedAccount`, but leaves it unchan `AUTHCALL` has the same gas cost as `CALL`. -As with `CALL`, the gas cost for the opcode itself (both the static and the dynamic portion) is always charged, independent of whether the call is actually executed. The gas passed into the call is calculated following EIP-150 and is refunded partially if the call returns with unused gas left, or completely if the call is not executed at all because of a failing pre-check. This also applies to the newly added pre-check for a zero `authorizedAccount` variable. +As with `CALL`, the gas cost for the opcode itself (both the static and the dynamic portion) is always charged, independent of whether the call is actually executed. The gas passed into the call is calculated following EIP-150 and is refunded partially if the call returns with unused gas left, or completely if the call is not executed at all because of a failing pre-check. ## Rationale From 0af37fe00da8d41935a344cb42baf6b6a47479de Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Sat, 13 Mar 2021 00:56:33 +0100 Subject: [PATCH 09/11] [EIP-3074] Updating Rationale for AUTHCALL --- EIPS/eip-3074.md | 64 +++++++++++++++--------------------------------- 1 file changed, 20 insertions(+), 44 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index c362c2c8a58dce..e73ecb9faa07a3 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -127,27 +127,13 @@ As with `CALL`, the gas cost for the opcode itself (both the static and the dyna ## Rationale -### Omitting Arguments +### Throwing for Unset `authorizedAccount` During `AUTHCALL` -The `invoker` signature argument is not included in the arguments to the instruction because it can be calculated by the instruction itself. - -### Two Return Values - -It is important to differentiate between a failure in `CALLFROM`'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 `CALLFROM 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 `CALLFROM` pre-conditions were invalidated, which implies reading and comparing memory values. Further, to remain sound if a callee were to revert with `CALLFROM failed`, `CALLFROM` 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 `CALLFROM` 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 `CALLFROM` to verify/adjust any state for replay protection. +Any correcly behaving contract should never reach an `AUTHCALL` without having successfully set `authorizedAccount` before. The correct behavior is thus to exit the current execution frame immediately. This is especially important in the context of transaction sponsoring / relaying, which is expected to be one of the main use cases for this EIP. There, being unable to distinguish between a sponsee-attributable failure (like a failing sub-call) and a sponsor-attributable failure (like a failing `AUTH`) is especially dangerous and thus must be prevented. ### 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. +While clients should never interpret EIP-3074 signed messages as transactions, reserving an [EIP-2718](./eip-2718.md) transaction type reduces the likelihood of this occuring by accident. ### Another Sponsored Transaction EIP @@ -155,23 +141,27 @@ Other approaches to sponsored transactions, which rely on introducing a new tran 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. -`CALLFROM`'s single purpose is to set `CALLER`. It implements the minimal functionality to enable sender abstraction for sponsored transactions. This single mindedness makes `CALLFROM` significantly more composable with existing Ethereum features. +`AUTHCALL`'s single deviation from `CALL` is to set `CALLER`. It implements the minimal functionality to enable sender abstraction for sponsored transactions. This single mindedness makes `AUTHCALL` significantly more composable with existing Ethereum features. -More logic can be implemented around the `CALLFROM` instruction, giving more control to invokers and sponsors without sacrificing security or user experience for sponsees. +More logic can be implemented around the `AUTHCALL` instruction, giving more control to invokers and sponsors without sacrificing security or user experience for sponsees. ### What to Sign? -Earlier approaches to this problem included mechanisms for replay protection, and also signed over value, gas, and other arguments to `CALLFROM`. Instead, this proposal explicitly delegates these responsibilities to the invoker contract. +Earlier approaches to this problem included mechanisms for replay protection, and also signed over value, gas, and other arguments to `AUTHCALL`. Instead, this proposal explicitly delegates these responsibilities to the invoker contract. + +As originally written, this proposal specified a precompile with storage to track nonces. Since a precompile with storage is unprecedented, a later revision moved replay protection into the invoker contract, necessitating a certain level of user trust in the invoker, while also opening the door to more creative replay protection schemes in the future. Building on this idea of trusted invokers, the other signed fields in the "transaction-like package" were eliminated until only `invoker` and `commit` remained. -As originally written, this proposal specified a precompile with storage to track nonces. Since a precompile with storage is unprecedented, a later revision moved replay protection into the invoker contract, necessitating a certain level of user trust in the invoker, while also opening the door to more creative replay protection schemes in the future. Building on this idea of trusted invokers, the other signed fields in the transaction-like package were eliminated until only `invoker` and `extra` remained. +The motivation for including `invoker` is to bind a particular signed message to a single invoker. If `invoker` was not part of the message, a malicious invoker could reuse the signature to impersonate the EOA. -The motivation for including `invoker` is to bind a particular transaction-like package to a single invoker. If `invoker` was not part of the TLP, a malicious invoker could reuse the TLP to impersonate the EOA. +Finally, `commit` should be used by invoker contracts to implement replay protection and security around calldata, value, and other parameters. For example, an invoker may assume `commit` to be `keccak256(abi.encode(gas, value, nonce))`, guaranteeing that the sponsee intended to set those parameters to those specific values. Without `commit`, invokers would not be able to determine if other values (eg. `gas`, `value`, calldata, etc.) had been tampered with. -Finally, `extra` should be used by invoker contracts to implement replay protection and security around calldata, value, and other parameters. For example, an invoker may assume `extra` to be `keccak256(abi.encode(gas, value, nonce))`, guaranteeing that the sponsee intended to set those parameters to those specific values. Without `extra`, invokers would not be able to determine if other values (eg. `gas`, `value`, calldata, etc.) had been tampered with. +### Banning `tx.origin` as Signer + +The reason for banning signatures from `tx.origin` is that subsequent `AUTHCALL`s would result in `msg.sender == tx.origin`. This however is a frequently used pattern to test for top-level execution (i.e. being called directly from an EOA). Banning `tx.origin` as signer keeps this invariant intact. ### On Call Depth -The EVM limits the maximum number of nested calls, and naively allowing a sponsor to manipulate the call depth before reaching the invoker would introduce a griefing attack against the sponsee. That said, with the 63/64th gas rule, and the cost of `CALLFROM`, the stack is effectively limited to a much smaller depth than the hard maximum by the `gas` parameter. +The EVM limits the maximum number of nested calls, and naively allowing a sponsor to manipulate the call depth before reaching the invoker would introduce a griefing attack against the sponsee. That said, with the 63/64th gas rule, and the cost of `AUTHCALL`, the stack is effectively limited to a much smaller depth than the hard maximum by the `gas` parameter. It is, therefore, sufficient for the invoker to guarantee a minimum amount of gas, because there is no way to reach the hard maximum call depth with any reasonable (i.e. less than billions) amount of gas. @@ -189,29 +179,15 @@ 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 - - - Potential impersonation attacks if there is a bug in the signature verification. - -### Invoker Considerations - The following is a non-exhaustive list of checks/pitfalls/conditions that invokers _should_ be wary of: - - Replay protection should be implemented by the invoker, and included in `extra`. Without it, a malicious sponsor can replay a TLP, repeating its effects. - - `value` should be included in `extra`. Without it, a malicious sponsor could cause unexpected effects in the callee. - - `gas` should be included in `extra`. Without it, a malicious sponsor could cause the callee to run out of gas and fail, griefing the sponsee. - - The current chain id should be included in `extra` and checked on every transaction. Without it, a malicious sponsor could replay a TLP on a different chain. - - `callee` and `calldata` should be included in `extra`. Without them, a malicious sponsor may call arbitrary functions in arbitrary contracts. - -A poorly implemented invoker can _allow a malicious sponsor to take near complete control over a sponsee's EOA_. - -### Frontrunning + - Replay protection should be implemented by the invoker, and included in `commit`. Without it, a malicious actor can reuse a signature, repeating its effects. + - `value` should be included in `commit`. Without it, a malicious sponsor could cause unexpected effects in the callee. + - `gas` should be included in `commit`. Without it, a malicious sponsor could cause the callee to run out of gas and fail, griefing the sponsee. + - The current chain id should be included in `commit` and checked on every transaction. Without it, a malicious sponsor could replay a signature on a different chain. + - `addr` and `calldata` should be included in `commit`. Without them, a malicious actor may call arbitrary functions in arbitrary contracts. - - Transaction-like packages can be extracted from the original sponsor's transaction and resent by another sponsor. +A poorly implemented invoker can _allow a malicious actor to take near complete control over a signer's EOA_. ## Copyright Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). From e5f5b68cc6d6c6697e6cda4d677d24d2694b71d6 Mon Sep 17 00:00:00 2001 From: Ansgar Dietrichs Date: Sat, 13 Mar 2021 12:07:03 +0100 Subject: [PATCH 10/11] [EIP-3074] Apply Sam's suggestions Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> --- EIPS/eip-3074.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index e73ecb9faa07a3..fa74abe599325e 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -11,7 +11,7 @@ created: 2020-10-15 ## Simple Summary -Creates two new EVM instructions that allow an externally owned account (EOA) to authorize a specific contract via an ECDSA signature to send calls on it's behalf. +Creates two new EVM instructions that authorize (via an ECDSA signature) a contract to act on behalf of an externally owned account. ## Abstract @@ -46,11 +46,13 @@ While it is possible to emulate sponsored transactions (ex. [Gas Station Network | ------------------- | --------- |:------------- | | `authorizedAccount` | `address` | unset | -`authorizedAccount` indicates the address of an account the executing contract is authorized to send calls from. An unset value indicates that no such account is set. The variable has the same scope as the program counter -- it persists throughout execution of the contract, but is not passed along any calls (including `DELEGATECALL`). +The context variable `authorizedAccount` shall indicate the active account for `AUTHCALL` instructions in the current frame of execution. If set, `authorizedAccount` shall only contain an account which has given the contract authorization to act on its behalf. An unset value shall indicate that no such account is set, and that there is not yet an active account for `AUTHCALL` instructions in the current frame of execution. + +The variable has the same scope as the program counter -- `authorizedAccount` persists throughout a single frame of execution of the contract, but is not passed through any calls (including `DELEGATECALL`). If the same contract is being executed in separate execution frames (ex. a `CALL` to self), both frames shall have independent values for `authorizedAccount`. Initially in each frame of execution, `authorizedAccount` is always unset, even if a previous execution frame for the same contract has a value. ### `AUTH` (`0xf6`) -A new opcode `AUTH` is created at `0xf6`. It takes in 4 stack elements and returns 1 stack element. +A new opcode `AUTH` shall be created at `0xf6`. It shall take four stack element inputs and returns one stack element. #### Input @@ -70,8 +72,8 @@ A new opcode `AUTH` is created at `0xf6`. It takes in 4 stack elements and retur #### Behavior The arguments (`yParity`, `r`, `s`) are interpreted as an ECDSA signature on the secp256k1 curve over the message `keccak256(TYPE || paddedInvokerAddress || commit)`, where: - - `paddedInvokerAddress` is the 32-byte right-alligned address of the contract executing `AUTH`, padded with zero-bytes. - - `commit` is one of the arguments passed into `AUTH` and is a 32-byte value that can be used to commit to specific additional validity condition in the invoker's pre-processing logic (e.g. a nonce for replay protection). + - `paddedInvokerAddress` is the address of the contract executing `AUTH`, left-padded with zeroes to a total of 32 bytes (ex. `0x000000000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`). + - `commit`, one of the arguments passed into `AUTH`, is a 32-byte value that can be used to commit to specific additional validity conditions in the invoker's pre-processing logic (e.g. a nonce for replay protection). If the signature is valid, the `signerAddress` is recovered. Signature validity and signer recovery is handled analogous to transaction signatures, including the stricter `s` range for preventing ECDSA malleability. Note that `yParity` is expected to be `0` or `1`. If `signerAddress != tx.origin`, the context variable `authorizedAccount` is set to `signerAddress`. In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to an unset value. @@ -84,7 +86,7 @@ The gas cost for `AUTH` is `3000`. This is the same cost as for the `ecrecover` ### `AUTHCALL` (`0xf7`) -A new opcode `AUTHCALL` is created at `0xf7`. It takes in 7 stack elements and returns 1 stack element. It matches the behavior of the existing `CALL` (`0xF1`) instruction, except where noted below. +A new opcode `AUTHCALL` shall be created at `0xf7`. It shall take seven stack elements and return one stack element. It matches the behavior of the existing `CALL` (`0xF1`) instruction, except where noted below. #### Input @@ -108,7 +110,7 @@ A new opcode `AUTHCALL` is created at `0xf7`. It takes in 7 stack elements and r `AUTHCALL` is interpreted the same as `CALL`, except for: - - If `authorizedAccount` is unset, execution is considered invalid and must exit the current execution frame immediately (in the same way it would do e.g. for a stack underflow or an invalid jump). + - If `authorizedAccount` is unset, execution is considered invalid and must exit the current execution frame immediately (in the same way as a stack underflow or invalid jump). - Otherwise, the caller address for the call is set to `authorizedAccount`. The call value is deducted from the balance of the executing contract. It is not paid by the `authorizedAccount`. @@ -129,7 +131,7 @@ As with `CALL`, the gas cost for the opcode itself (both the static and the dyna ### Throwing for Unset `authorizedAccount` During `AUTHCALL` -Any correcly behaving contract should never reach an `AUTHCALL` without having successfully set `authorizedAccount` before. The correct behavior is thus to exit the current execution frame immediately. This is especially important in the context of transaction sponsoring / relaying, which is expected to be one of the main use cases for this EIP. There, being unable to distinguish between a sponsee-attributable failure (like a failing sub-call) and a sponsor-attributable failure (like a failing `AUTH`) is especially dangerous and thus must be prevented. +A well-behaved contract should never reach an `AUTHCALL` without having successfully set `authorizedAccount` beforehand. The safest behavior, therefore, is to exit the current frame of execution immediately. This is especially important in the context of transaction sponsoring / relaying, which is expected to be one of the main use cases for this EIP. In a sponsored transaction, the inability to distinguish between a sponsee-attributable fault (like a failing sub-call) and a sponsor-attributable fault (like a failing `AUTH`) is especially dangerous and should be prevented because it charges unfair fees to the sponsee. ### Reserving an [EIP-2718](./eip-2718.md) Transaction Type From 7b74319bbafdc80cdd699c23bb9a3ea07707a9eb Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Sun, 14 Mar 2021 14:43:09 -0600 Subject: [PATCH 11/11] Update EIPS/eip-3074.md --- EIPS/eip-3074.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-3074.md b/EIPS/eip-3074.md index fa74abe599325e..4353e6af48e0e1 100644 --- a/EIPS/eip-3074.md +++ b/EIPS/eip-3074.md @@ -135,7 +135,7 @@ A well-behaved contract should never reach an `AUTHCALL` without having successf ### Reserving an [EIP-2718](./eip-2718.md) Transaction Type -While clients should never interpret EIP-3074 signed messages as transactions, reserving an [EIP-2718](./eip-2718.md) transaction type reduces the likelihood of this occuring by accident. +While clients should never interpret EIP-3074 signed messages as transactions, reserving an [EIP-2718](./eip-2718.md) transaction type reduces the likelihood of this occurring by accident. ### Another Sponsored Transaction EIP