Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
bizliaoyuan authored Feb 16, 2024
2 parents 899cb0f + 511b7fc commit c23f054
Showing 1 changed file with 98 additions and 11 deletions.
109 changes: 98 additions & 11 deletions ERCS/erc-5189.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ To avoid Ethereum consensus changes, we do not attempt to create new transaction
| endorserGasLimit | uint64 | amount of gas that should be passed to the endorser when validating the `operation`. |
| maxFeePerGas | uint256 | max amount of basefee that the `operation` execution is expected to pay. _(Similar to [EIP-1559](./eip-1559.md) `max_fee_per_gas`)_. |
| priorityFeePerGas | uint256 | fixed amount of fees that the `operation` execution is expected to pay to the bundler. _(Similar to [EIP-1559](./eip-1559.md) `max_priority_fee_per_gas`)_. |
| baseFeeScalingFactor | uint256 | Scaling factor to convert `block.basefee` into the `feeToken` unit. |
| baseFeeNormalizationFactor | uint256 | Normalization factor to convert `block.basefee` into the `feeToken` unit. |
| hasUntrustedContext | bool | If `true`, the operation *may* have untrusted code paths. These should be treated differently by the bundler (see untrusted environment). |

These `Operation` objects can be sent to a dedicated operations mempool. A specialized class of actors called bundlers (either block producers running special-purpose code, or just users that can relay transactions to block producers) listen for operations on the mempool and execute these transactions.

Expand All @@ -62,9 +65,19 @@ This categorization is facilitated by the `endorser`; the endorser must be a dep

```solidity
interface Endorser {
struct BlockDependency {
uint256 maxNumber;
uint256 maxTimestamp;
struct GlobalDependency {
bool basefee;
bool blobbasefee;
bool chainid;
bool coinbase;
bool difficulty;
bool gasLimit;
bool number;
bool timestamp;
bool txOrigin;
bool txGasPrice;
uint256 maxBlockNumber;
uint256 maxBlockTimestamp;
}
struct Constraint {
Expand All @@ -90,10 +103,13 @@ interface Endorser {
uint256 _gasLimit,
uint256 _maxFeePerGas,
uint256 _maxPriorityFeePerGas,
address _feeToken
address _feeToken,
uint256 _baseFeeScalingFactor,
uint256 _baseFeeNormalizationFactor,
bool _hasUntrustedContext
) external returns (
bool readiness,
BlockDependency memory blockDependency,
GlobalDependency memory globalDependency,
Dependency[] memory dependencies
);
}
Expand All @@ -104,10 +120,14 @@ Endorsers SHOULD be registered in the `EndorserRegistry` with a minimum amount o
When the `isOperationReady` method is called, the endorser must return this information:

* **readiness:** when returning `true`, it means the transaction WILL be executed correctly and the bundler WILL be paid the offered gas fees (even if the underlying intent of the operation fails).
* **blockDependency:** maximum values for block values; once the block reaches these values, the `readiness` result MUST be re-evaluated.
* **globalDependency:** a list of possible dependencies that don't belong to a given address, defines if the execution of the transaction MAY be invalidated by a change on one of these global variables, `maxBlockNumber` and `maxBlockTimestamp` are used as global constraints.
* **dependencies:** a comprehensive list of addresses and storage slots that must be monitored; any state change in these dependencies MUST trigger a re-evaluation of the operation's readiness.

The information provided by the endorser helps the mempool operator maintain a pool of "good" AA transactions that behave correctly; it DOES NOT guarantee that such transactions will be able to be executed correctly. Bundlers must always simulate the result of the execution before including a transaction in a block. They can alternatively monitor the dependencies for changes induced by other transactions in the mempool.
The information provided by the endorser helps the mempool operator maintain a pool of "good" AA transactions that behave correctly; but it only provides a soft guarantee that the transaction will be executed correctly. Bundlers must always simulate the result of the execution before including a transaction in a block.

If the result of the last simulation fails, but the endorser still returns `readiness == true`, then the endorser can not be trusted and it must be banned by the mempool operator.

The dependency list serves as a shortcut for the bundler to know which operations are fully independent from each other. This shortcut is useful for (a) clearing the mempool from operations that are no longer valid, and (b) for bundlers to know which operations can be included in the same block.

For efficiency, additional information CAN be provided to the endorser with `_endorserCallData`.
If used, the endorser MUST validate that the provided `_endorserCallData` is valid and relevant to the other values provided.
Expand Down Expand Up @@ -143,7 +163,7 @@ Note that `allSlots`, `constraints` and `slots` are mutually exclusive. If `allS
If a slot is listed in `constraints`, it must not be listed in `slots`.
The `endorser` should prefer to use `constraints` over `slots`, and `slots` over `allSlots` whenever possible.

> E.g. A wallet may pay fees using funds stored as WETH. During `isValidOperation()`, the endorser contract may call the `balanceOf` method of the `WETH` contract to determine if the wallet has enough `WETH` balance. Even though the ETH balance of the WETH contract and the code of the WETH contract are being accessed, the endorser only cares about the user's WETH balance for this operation and hence does not include these as dependencies.
> E.g. A wallet may pay fees using funds stored as WETH. During `isOperationReady()`, the endorser contract may call the `balanceOf` method of the `WETH` contract to determine if the wallet has enough `WETH` balance. Even though the ETH balance of the WETH contract and the code of the WETH contract are being accessed, the endorser only cares about the user's WETH balance for this operation and hence does not include these as dependencies.
#### Constraints

Expand All @@ -153,7 +173,7 @@ The `endorser` should prefer to use `constraints` over `slots`, and `slots` over
| minValue | bytes32 | Minimum value (inclusive) of `slot` that `readiness` applies to. |
| maxValue | bytes32 | Maximum value (inclusive) of `slot` that `readiness` applies to. |

The `endorser` can use the `minValue` and `maxValue` fields to limit the validity of the `readiness` result. This allows the bundler to avoid re-evaluating the `operation` for some slot changes.
The `endorser` can use the `minValue` and `maxValue` fields to limit the validity of the `readiness` result. This allows the endorser to fully validate an operation, even when this operation depends on storage values that are not directly accessible by the endorser.

When an exact value is required, `minValue` and `maxValue` should be set to the same value.

Expand All @@ -174,7 +194,74 @@ If, when simulating the final inclusion of the operation, the bundler discovers

After an `endorser` is banned, the mempool operator should drop all `operations` related to such endorser.

> Notice: The mempool operator could call one last time `isOperationReady` to determine if the `endorser` should be banned because `(1)` or `(2)`, but this step is not strictly necessary since both scenarios lead to the `endoser` being banned.
### Untrusted environment

In some scenarios, the endorser may not be able to fully validate the operation but may be able to infer that a given code path *should* be safe. In these cases, the endorser can mark a section of the operation as `untrusted`. Any storage slots (balance, code, nonce, or specific slots) accessed in this untrusted context should be automatically considered as dependencies.

```sol
interface Endorser {
event UntrustedStarted();
event UntrustedEnded();
}
```

The endorser can use the `UntrustedStarted` and `UntrustedEnded` events to signal the start and end of an untrusted context. The bundler should listen to these events and extend the dependencies list accordingly.

Only the top-level endorser can signal an untrusted context; any other events with the same signature but emitted by a different contract should be ignored.

If multiple events are emitted, the bundler should count the number of `UntrustedStarted` and `UntrustedEnded` events and only consider the untrusted context as ended when the number of `UntrustedEnded` events is equal to the number of `UntrustedStarted` events.

Untrusted contexts can be opened and closed multiple times and can be nested.

If `hasUntrustedContext` is set to `false`, the bundler should ignore any `UntrustedStarted` and `UntrustedEnded` events.

#### Automatic dependency graph construction

All code executed within the untrusted environment must be monitored. If the code executes any of the following opcodes, the dependency graph must be extended accordingly.

| Opcode | Dependency |
|-------------|-----------------------------------------|
| BALANCE | `dependencies[addr].balance = true` |
| ORIGIN | `global.txOrigin = true` |
| CODESIZE | None |
| CODECOPY | None |
| GASPRICE | `global.txGasPrice = true` |
| EXTCODESIZE | `dependencies[addr].code = true` |
| EXTCODECOPY | `dependencies[addr].code = true` |
| EXTCODEHASH | `dependencies[addr].code = true` |
| COINBASE | `global.coinbase = true` |
| TIMESTAMP | `global.timestamp = true` |
| NUMBER | `global.number = true` |
| DIFFICULTY | `global.difficulty = true` |
| PREVRANDAO | `global.difficulty = true` |
| CHAINID | `global.chainid = true` |
| SELFBALANCE | `dependencies[self].balance = true` |
| BASEFEE | `global.basefee = true` |
| SLOAD | `dependencies[addr].slots[slot] = true` |
| CREATE | `dependencies[addr].nonce = true` |
| CREATE2 | `dependencies[contract].code = true` |

Notice that untrusted environments generate a lot of dependencies and may generate many false positives. This may lead to numerous re-evaluations and thus to the operation being dropped from the mempool.

Block-level dependencies are specially sensitive as they will be shared with a large number of operations.

It is recommended to use untrusted environments only when necessary, like when an endorser needs to validate a nested signature to a wallet that is not under its control.

### Fee payment

The operation is expected to pay at least `gasUsed * effectiveGasPrice` to `tx.origin`, the `gasUsed` includes both the execution cost of the operation and the cost of its calldata.

The payment is always made in the `feeToken`.

The `maxFeePerGas` and `priorityFeePerGas` are expressed in the unit of the `feeToken`.

The effective gas price is calculated as follows:

```
effectiveGasPrice = Min(maxFeePerGas, ((block.baseFee * _baseFeeScalingFactor) / _baseFeeNormalizationFactor) + priorityFeePerGas)
```

`block.baseFee` is the base fee of the block in the native token unit, and `_baseFeeScalingFactor` and `_baseFeeNormalizationFactor` are used to convert the base fee into the `feeToken` unit.

### Bundler behavior upon receiving an operation

Expand All @@ -189,7 +276,7 @@ When a bundler receives an `operation`, it SHOULD perform these sanity checks:
* The `maxFeePerGas` and `priorityPerGas` are above a configurable minimum value the bundler is willing to accept.
* If another operation exists in the mempool with the exact same dependency set AND the same endorser address, the `maxFeePerGas` and `priorityFeePerGas` of the newly received operation MUST be 12% higher than the one on the mempool to replace it. (Similar with how EOA with same nonce work)

If the `operation` passes these checks, then the bundler MUST call `isOperationReady()` on the `endorser`. If the endorser considers the operation ready, then the client MUST add the operation to the mempool. Otherwise, the operation MUST be dropped.
If the `operation` passes these checks, then the bundler MUST call `isOperationReady()` on the `endorser`. If the endorser considers the operation ready, and the constraints are within bounds, then the client MUST add the operation to the mempool. Otherwise, the operation MUST be dropped.

The `endorser` result SHOULD be invalidated and its readiness SHOULD be re-evaluated if any of the values of the provided dependencies change. If the operation readiness changes to `false`, the operation MUST be discarded.

Expand Down

0 comments on commit c23f054

Please sign in to comment.