diff --git a/docs/ethsimulatev1-notes.md b/docs/ethsimulatev1-notes.md new file mode 100644 index 000000000..a4972b5e2 --- /dev/null +++ b/docs/ethsimulatev1-notes.md @@ -0,0 +1,202 @@ +# eth_simulate +This document contains some extra information that couldn't be fit to the specification document directly. + +## Default block values +Unlike `eth_call`, `eth_simulateV1`'s calls are conducted inside blocks. We don't require user to define all the fields of the blocks so here are the defaults that are assumed for blocks parameters: + +| parameter name | default value | +-----------------|----------------------- +| prevRandao | `0x0000000000000000000000000000000000000000000000000000000000000000` | +| feeRecipient | `0x0000000000000000000000000000000000000000` | +| mixHash | `0x0000000000000000000000000000000000000000000000000000000000000000` | +| nonce | `0x0` | +| extraData | `0x0000000000000000000000000000000000000000000000000000000000000000` | +| difficulty | The same as the base block defined as the second parameter in the call | +| gasLimit | The same as the base block defined as the second parameter in the call | +| hash | Calculated normally | +| parentHash | Previous blocks hash | +| timestamp | The timestamp of previous block + `network block time` (12s on Ethereum Mainnet) | +| baseFeePerGas | When validation mode is true, baseFeePerGas is calculated on what it should be according to Ethereum's spec. When validation mode is false, the baseFeePerGas is set to zero | +| sha3Uncles | Empty trie root | +| withdrawals | Empty array | +| uncles | Empty array | +| blobBaseFee | When validation mode is true, blobBaseFee is calculated on what it should be according to EIP-4844 spec. When validation mode is false, the blobBaseFee is set to zero | +| number | Previous block number + 1 | +| logsBloom | Calculated normally. ETH logs are not part of the calculation | +| receiptsRoot | Calculated normally | +| transactionsRoot | Calculated normally | +| size | Calculated normally | +| withdrawalsRoot | Calculated normally | +| gasUsed | Calculated normally | +| stateRoot | Calculated normally | + +## Default values for transactions +As eth_simulate is an extension to `eth_call` we want to enable the nice user experience that the user does not need to provide all required values for a transaction. We are assuming following defaults if the variable is not provided by the user: +| parameter name | description | +-----------------|----------------------- +| type | `0x2` | +| nonce | Take the correct nonce for the account prior eth_simulate and increment by one for each transaction by the account | +| to | `null` | +| from | `0x0000000000000000000000000000000000000000` | +| gasLimit | blockGasLimit - soFarUsedGasInBlock | +| value | `0x0` | +| input | no data | +| gasPrice | `0x0` | +| maxPriorityFeePerGas | `0x0` | +| maxFeePerGas | `0x0` | +| accessList | empty array | +| blobVersionedHashes | empty array | +| chainId | The chain id of the current chain | +| r | `0x0` | +| s | `0x0` | +| yParity | even | +| v | `0x0` | + +## Overriding default values +The default values of blocks and transactions can be overriden. For Transactions we allow overriding of variables `type`, `nonce`, `to`, `from`, `gas limit`, `value`, `input`, `gasPrice`, `maxPriorityFeePerGas`, `maxFeePerGas`, `accessList`, and for blocks we allow modifications of `number`, `time`, `gasLimit`, `feeRecipient`, `prevRandao`, `baseFeePerGas` and `blobBaseFee`: +```json +"blockOverrides": { + "number": "0x14", + "time": "0xc8", + "gasLimit": "0x2e631", + "feeRecipient": "0xc100000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000001234", + "baseFeePerGas": "0x14", + "blobBaseFee": "0x15" +}, +``` +All the other fields are computed automatically (eg, `stateRoot` and `gasUsed`) or kept as their default values (eg. `uncles` or `withdrawals`). When overriding `number` and `time` variables for blocks, we automatically check that the block numbers and time fields are strictly increasing (we don't allow decreasing, or duplicated block numbers or times). If the block number is increased more than `1` compared to the previous block, new empty blocks are generated in between. + +An interesting note here is that an user can specify block numbers and times of some blocks, but not for others. When block numbers of times are left unspecified, the default values will be used. After the blocks have been constructed, and default values are calculated, the blocks are checked that their block numbers and times are still valid. + +## ETH transfer logs +When `traceTransfers` setting is enabled on `eth_simulateV1`, eth_simulate will return logs for ethereum transfers along with the normal logs sent by contracts. The ETH transfers are identical to ERC20 transfers, except the "sending contract" is address `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`. + +For example, here's a query that will simply send ether from one address to another (with a state override that gives us the ETH initially): +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "eth_simulateV1", + "params": [ + { + "blockStateCalls": [ + { + "stateOverrides": { + "0xc000000000000000000000000000000000000000": { + "balance": "0x7d0" + } + }, + "calls": [ + { + "from": "0xc000000000000000000000000000000000000000", + "to": "0xc100000000000000000000000000000000000000", + "value": "0x3e8" + } + ] + } + ], + "traceTransfers": true + }, + "latest" + ] +} +``` + +The output of this query is: +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "number": "0x4", + "hash": "0x859c932c5cf0dabf8d12eb2518e063966ac1a25e2fc49f1f02574a37f358d0b5", + "timestamp": "0x1f", + "gasLimit": "0x4c4b40", + "gasUsed": "0x5208", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "0x2310a91d", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "calls": [ + { + "returnData": "0x", + "logs": [ + { + "address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000c000000000000000000000000000000000000000", + "0x000000000000000000000000c100000000000000000000000000000000000000" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000003e8", + "blockNumber": "0x4", + "transactionHash": "0xa4d41019e71335f8567e17746b708ddda8b975a9a61f909bd3df55f4866cc913", + "transactionIndex": "0x0", + "blockHash": "0x859c932c5cf0dabf8d12eb2518e063966ac1a25e2fc49f1f02574a37f358d0b5", + "logIndex": "0x0", + "removed": false + } + ], + "gasUsed": "0x5208", + "status": "0x1" + } + ] + } + ] +} +``` + +Here the interesting part is: +```json +"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", +"topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000c000000000000000000000000000000000000000", + "0x000000000000000000000000c100000000000000000000000000000000000000" +], +"data": "0x00000000000000000000000000000000000000000000000000000000000003e8", +``` +In the observed event, the sender address is denoted as the `0xee...` address. The first topic (`0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef`) aligns with the event signature `Transfer(address,address,uint256)`, while the second topic (`0x000000000000000000000000c000000000000000000000000000000000000000`) corresponds to the sending address, and the third topic (`0x000000000000000000000000c100000000000000000000000000000000000000`) represents the receiving address. The quantity of ETH transacted is stored in the data field. + +The ETH logs will contain following types of ETH transfers: + - Transfering ETH from EOA + - Transfering ETH via contract + - Selfdestructing contract sending ETH + +But not following ones: + - Gas fees + - eth_simulates eth balance override + +ETH logs are not part of the calculation for logs bloom filter. Also, similar to normal logs, if the transaction sends ETH but the execution reverts, no log gets issued. + +## Validation Mode +The `eth_simulate` includes a feature to enable or disable validation using the `Validation` setting. By default, validation is off, and `eth_simulate` behaves similarly to `eth_call`. When validation is enabled, the simulation closely approximates real EVM block creation, except it skips transaction signature checks and allows direct transactions from contracts. + +### Zero Base Fee +When validation mode is disabled, the block's `baseFee` is set to zero. This affects the BASEFEE opcode and the `baseFee` returned by `eth_simulate`. Additionally, the base fee does not auto-adjust based on the parent block, even if the user overrides the previous block's `baseFee`. + +This behavior is intentional to enable free transactions in `eth_simulate`, allowing users to avoid adding boilerplate code to give themselves ETH using a balance override. While similar results could be achieved by skipping balance validation, it would be inconsistent with EVM operations. + +## Failures +It is possible that user defines a transaction that cannot be included in the Ethereum block as it breaks the rules of EVM. For example, if transactions nonce is too high or low, baseFeePerGas is too low etc. In these situations the execution of eth_simulate ends and an error is returned. + +## Version number +The method name for eth_simulate `eth_simulateV1` the intention is that after release of eth_simulate, if new features are wanted the `eth_simulateV1` is kept as it is, and instead `eth_simulateV2` is published with the new wanted features. + +## Clients can set their own limits +Clients may introduce their own limits to prevent DOS attacks using the method. We have thought of three such standard limits +- How many blocks can be defined in `BlockStateCalls`. The suggested default for this is 256 blocks +- A global gas limit (similar to the same limit for `eth_call`). The eth_simulate cannot exceed the global gas limit over its lifespan +- The clients can set their own limit on how big the input JSON payload can be. A suggested default for this is 30mb + +## Rationale + +### Pre-computed calls + +When it comes to contract override behavior, specifically precompile override, there were two approaches: + +1. As specified above to allow replacement of precompiles by EVM code and to allow those same precompiles to be relocated to another access for fallback behavior. +2. Allow users to pass in a set of pre-computed calls for an address, i.e. direct mapping of input to output. + +The second approach has better UX for simple use-cases such as faking a signature via ecrecover. It also allows for getting the same gas usage as a real precompile execution. The simpler UX comes at cost of flexibility. Overriding with EVM code is general. It also imposes changes to the EVM interpreter code, which has been otherwise avoided, without enabling new features. Hence the spec proposes the first alternative. diff --git a/src/eth/execute.yaml b/src/eth/execute.yaml index 0f7667aea..866cb901f 100644 --- a/src/eth/execute.yaml +++ b/src/eth/execute.yaml @@ -95,3 +95,19 @@ storageKeys: - '0x0000000000000000000000000000000000000000000000000000000000000081' gasUsed: '0x125f8' +- name: eth_simulateV1 + summary: Executes a sequence of message calls building on each other's state without creating transactions on the block chain, optionally overriding block and state data + params: + - name: Payload + required: true + schema: + $ref: '#/components/schemas/EthSimulatePayload' + - name: Block tag + required: false + description: "default: 'latest'" + schema: + $ref: '#/components/schemas/BlockNumberOrTagOrHash' + result: + name: Result of calls + schema: + $ref: '#/components/schemas/EthSimulateResult' diff --git a/src/schemas/execute.yaml b/src/schemas/execute.yaml new file mode 100644 index 000000000..460584b8d --- /dev/null +++ b/src/schemas/execute.yaml @@ -0,0 +1,415 @@ +EthSimulatePayload: + title: Arguments for multi call + required: + - blockStateCalls + properties: + blockStateCalls: + title: Block State Calls + description: Definition of blocks that can contain calls and overrides + $ref: '#/components/schemas/BlockStateCalls' + traceTransfers: + title: Trace ETH Transfers + description: |- + Adds ETH transfers as ERC20 transfer events to the logs. These transfers have emitter contract parameter set as address(0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee). + Default: false. + type: boolean + validation: + title: Validation + description: |- + When true, the eth_simulateV1 does all validations that a normal EVM would do, except contract sender and signature checks. When false, eth_simulateV1 behaves like eth_call. + Default: false. + type: boolean + returnFullTransactions: + title: Return Full Transactions + description: |- + When true, the method returns full transaction objects, otherwise, just hashes are returned. + type: boolean +BlockStateCalls: + title: Array of block state calls to be executed at specific, optional block/state. + description: "The size of this array may be limited depending on the client as a DOS protection. 256 is a common/recommended limit as it is the same limit used by BLOCKHASH opcode." + type: array + properties: + blockOverrides: + title: Block overrides + description: |- + Block overrides can be used to replace fields in a block. + default: no block override. + $ref: '#/components/schemas/BlockOverrides' + stateOverrides: + title: State overrides + description: |- + State overrides can be used to replace existing blockchain state with new state. + Default: no state overrides + $ref: '#/components/schemas/StateOverrides' + calls: + type: array + title: calls + description: |- + List of transactions to execute at this block/state. + Default: [] + items: + $ref: '#/components/schemas/GenericCallTransaction' +StateOverrides: + title: Dictionary of addresses in the state to be overridden + type: object + patternProperties: + '^0x[a-fA-F0-9]{40}$': + $ref: '#/components/schemas/AccountOverride' + additionalProperties: false +AccountOverride: + title: Details of an account to be overridden + type: object + oneOf: + - $ref: '#/components/schemas/AccountOverrideState' + - $ref: '#/components/schemas/AccountOverrideStateDiff' +AccountOverrideState: + title: Account override with whole storage replacement + description: It is possible to override any kind of address (EOA's, contracts and precompiles) + required: + - state + properties: + nonce: + title: Nonce + $ref: '#/components/schemas/uint64' + balance: + title: Balance + $ref: '#/components/schemas/uint256' + code: + title: Code + $ref: '#/components/schemas/bytes' + movePrecompileToAddress: + title: MovePrecompileToAddress + description: Moves addresses precompile into the specified address. This move is done before the 'code' override is set. When the specified address is not a precompile, the behaviour is undefined and different clients might behave differently. + $ref: '#/components/schemas/address' + state: + title: Storage + description: Key-value mapping to override all slots in the account storage before executing the call. This functions similar to eth_call's state parameter. + $ref: '#/components/schemas/AccountStorage' +AccountOverrideStateDiff: + title: Account override with partial storage modification + required: + - stateDiff + properties: + nonce: + title: Nonce + $ref: '#/components/schemas/uint64' + balance: + title: Balance + $ref: '#/components/schemas/uint256' + code: + title: Code + $ref: '#/components/schemas/bytes' + movePrecompileToAddress: + title: MovePrecompileToAddress + $ref: '#/components/schemas/address' + description: Moves addresses precompile into the specified address. This move is done before the 'code' override is set. Can only move precompiles. + stateDiff: + title: Storage difference + description: Key-value mapping to override individual slots in the account storage before executing the call. This functions similar to eth_call's state parameter. + $ref: '#/components/schemas/AccountStorage' +AccountStorage: + title: Storage slots for an account + type: object + patternProperties: + '^0x[a-fA-F0-9]{64}$': + $ref: '#/components/schemas/hash32' + additionalProperties: false +BlockOverrides: + title: Context fields related to the block being executed + type: object + properties: + number: + title: Number + $ref: '#/components/schemas/uint64' + description: When overriding block numbers across multiple blocks, block number need to be increasing. Skipping over blocks numbers is possible. If block number is not specified, it's incremented by one for each block. + prevRandao: + title: The Previous value of randomness beacon + $ref: '#/components/schemas/uint256' + time: + title: Time + $ref: '#/components/schemas/uint64' + description: Time must either increase or remain constant relative to the previous block. If time is not specified, it's incremented by one for each block. + gasLimit: + title: Gas limit + $ref: '#/components/schemas/uint64' + feeRecipient: + title: Fee Recipient (also known as coinbase) + $ref: '#/components/schemas/address' + baseFeePerGas: + title: Base fee per unit of gas + $ref: '#/components/schemas/uint256' + withdrawals: + title: Withdrawals made by validators + $ref: '#/components/schemas/Withdrawals' + blobBaseFee: + title: Base fee per unit of blob gas + $ref: '#/components/schemas/uint64' +Withdrawals: + title: Withdrawals made by validators + description: This array can have a maximum length of 16. + type: array + items: + - $ref: '#/components/schemas/Withdrawal' +Withdrawal: + title: A withdrawal made by a validator + type: object + properties: + index: + title: index + $ref: '#/components/schemas/uint64' + validatorIndex: + title: Validator Index + $ref: '#/components/schemas/uint64' + address: + title: Address + $ref: '#/components/schemas/address' + amount: + title: Amount + $ref: '#/components/schemas/uint64' +EthSimulateResult: + title: Full results of multi call + type: object + oneOf: + - $ref: '#/components/schemas/EthSimulateBlockResultInvalid' + - $ref: '#/components/schemas/EthSimulateBlockResultSuccess' +EthSimulateBlockResultSuccess: + title: Full results of multi call + type: array + items: + $ref: '#/components/schemas/EthSimulateBlockResultSingleSuccess' +EthSimulateBlockResultSingleSuccess: + title: Result of eth_simulate block-level, with array of calls + type: object + allOf: + - $ref: '#/components/schemas/Block' + - title: Eth Simulate call results + required: + - calls + properties: + calls: + title: Call Results + $ref: '#/components/schemas/CallResults' +EthSimulateBlockResultInvalid: + title: Result of eth_simulate not being valid + description: The error messages are suggestions and a client might decide to return a different errror message than specified here. However, the error codes are enforced by this specification. + type: object + required: + - error + properties: + error: + oneOf: + - code: -32000 + message: Invalid request + - code: -32602 + message: Missing or invalid parameters + - code: -32005 + message: Transactions maxFeePerGas is too low + - code: -32015 + messagE: Execution error + - code: -32016 + message: Timeout + - code: -32603 + message: The Ethereum node encountered an internal error + - code: -38010 + message: Transactions nonce is too low + - code: -38011 + message: Transactions nonce is too high + - code: -38012 + message: Transactions baseFeePerGas is too low + - code: -38013 + message: Not enough gas provided to pay for intrinsic gas for a transaction + - code: -38014 + message: Insufficient funds to pay for gas fees and value for a transaction + - code: -38015 + message: Block gas limit exceeded by the block's transactions + - code: -38020 + message: Block number in sequence did not increase + - code: -38021 + message: Block timestamp in sequence did not increase or stay the same + - code: -38022 + message: MovePrecompileToAddress referenced itself in replacement + - code: -38023 + message: Multiple MovePrecompileToAddress referencing the same address to replace + - code: -38024 + message: Sender is not an EOA + - code: -38025 + message: Max init code size exceeded + - code: -38026 + message: Client adjustable limit exceeded +CallResults: + title: Results of multi call within block + type: array + items: + oneOf: + - $ref: '#/components/schemas/CallResultFailure' + - $ref: '#/components/schemas/CallResultSuccess' +CallResultFailure: + title: Result of call failure + description: The error messages are suggestions, and clients might implement different error messages. However, the error codes are enforced by the spec. + type: object + required: + - status + - returnData + - gasUsed + - error + properties: + status: + title: Call Status Failure + type: string + pattern: ^0x0$ + returnData: + title: Return data + $ref: '#/components/schemas/bytes' + gasUsed: + title: Return gasUsed + $ref: '#/components/schemas/uint64' + error: + oneOf: + - code: -32000 + message: 'Execution reverted' + - code: -32015 + message: 'VM execution error' +CallResultSuccess: + title: Result of call success + type: object + required: + - status + - returnData + - gasUsed + - logs + properties: + status: + title: Call Status Success + type: string + pattern: ^0x1$ + returnData: + title: Return data + $ref: '#/components/schemas/bytes' + gasUsed: + title: Return gasUsed + $ref: '#/components/schemas/uint64' + logs: + title: Return logs + type: array + items: + $ref: '#/components/schemas/CallResultLog' +CallResultLog: + title: log + type: object + required: + - logIndex + - blockhash + - blockNumber + - transactionHash + - transactionIndex + - address + - data + - topics + properties: + logIndex: + title: log index + $ref: '#/components/schemas/uint256' + blockHash: + title: block hash + $ref: '#/components/schemas/hash32' + blockNumber: + title: block number + $ref: '#/components/schemas/uint64' + transactionHash: + title: transaction hash + $ref: '#/components/schemas/hash32' + transactionIndex: + title: transaction index + $ref: '#/components/schemas/uint256' + address: + title: address + description: When trace transfers is enabled, this field is address(0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee) for ETH transfers. + $ref: '#/components/schemas/address' + data: + title: data + $ref: '#/components/schemas/bytes' + topics: + title: topics + type: array + items: + $ref: '#/components/schemas/bytes32' + removed: + title: removed + type: boolean + description: |- + Default: False. The flag is always False if present. A flag indicating if a log was removed in a chain reorganization, which cannot happen in eth_simulateV1. +GenericCallTransaction: + type: object + title: Transaction object type for call + properties: + type: + title: type + $ref: '#/components/schemas/byte' + description: |- + Default: 0x2 + nonce: + title: nonce + description: |- + Default: Defaults to correct nonce + $ref: '#/components/schemas/uint64' + to: + title: to address + $ref: '#/components/schemas/address' + description: |- + Default: 0x0 + from: + title: from address + $ref: '#/components/schemas/address' + description: |- + Default: null + gas: + title: gas limit + description: |- + Default: Remaining gas in the current block + $ref: '#/components/schemas/uint64' + value: + title: value + description: |- + Default: 0 + $ref: '#/components/schemas/uint256' + input: + title: input data + description: |- + Default: no data + $ref: '#/components/schemas/bytes' + gasPrice: + title: gas price + description: |- + The gas price willing to be paid by the sender in wei + Default: 0 + $ref: '#/components/schemas/uint256' + maxPriorityFeePerGas: + title: max priority fee per gas + description: |- + Maximum fee per gas the sender is willing to pay to miners in wei + Default: 0 + $ref: '#/components/schemas/uint256' + maxFeePerGas: + title: max fee per gas + description: |- + The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei + Default: 0 + $ref: '#/components/schemas/uint256' + maxFeePerBlobGas: + title: max fee per blob gas + description: |- + The maximum total fee per blob gas the sender is willing to pay in wei + Default: 0 + $ref: '#/components/schemas/uint256' + accessList: + title: accessList + description: |- + EIP-2930 access list + Default: [] + $ref: '#/components/schemas/AccessList' + blobVersionedHashes: + title: Blob versioned hashes + description: |- + EIP-4844 versioned hashes + Default: [] + $ref: '#/components/schemas/bytes32' diff --git a/wordlist.txt b/wordlist.txt index 9937df3d4..4fe193f3c 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -67,5 +67,11 @@ randao src https forkchoiceupdatedresponsev +feeRecipient +multicallV +EOA +EVM +ERC +VM exitv txs \ No newline at end of file