-
Notifications
You must be signed in to change notification settings - Fork 390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add eth_multicall , support array of eth_call for simulation across multiple blocks #383
Closed
Closed
Changes from all commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
5556499
Add eth_multicall
epheph 95822af
Remove chain-id from call-type (no replay protection necessary for ca…
epheph 602d2bf
Merge remote-tracking branch 'origin/main' into eth_multicall
epheph 6ce85e2
Add tests
epheph 2fcbf1d
Add test error case
epheph 046502a
Fix account override adding address to target
epheph e4ec926
Add example resetting WETH to 1ETH totalSupply and assigning that 1ET…
epheph 0fbdf40
Add another account delta
epheph ccfc70b
Add example resetting WETH to 1ETH totalSupply and assigning that 1ET…
epheph 7590b80
block override with simple contract that just returns block number
epheph bbf34e8
Change response to keep the block-based structure similar to request …
epheph c147f92
Update src/schemas/execute.yaml
epheph 23ef5b8
Update src/schemas/execute.yaml
epheph 73897a7
Update src/schemas/execute.yaml
epheph 152cf09
Update src/schemas/execute.yaml
epheph c8abc82
Get rid of version, consider standard solution in #408 for the future
epheph 14e9b35
Even though it's only 1 block you are overriding in this context, you…
epheph 94786e2
Addressing feedback related to return being reserved word
epheph 4b63f49
if no logs, empty array.
epheph dc38150
Most restrictive data type for blockNumber i could find elsewhere
epheph 9463ba6
Most restrictive data type for gas I could find elsewhere in repo
epheph 51fad35
Apply suggestions from code review
epheph 1802812
Update src/eth/execute.yaml
epheph 6bd8e50
Update src/eth/execute.yaml
epheph 2cf1b8e
Add Trace Transfers argument
epheph ff39b4a
Add moveToAddress, a way to displace code or precompile at a specific…
epheph 81e6730
Add tiny documentation inline concerning requirement that block numbe…
epheph 4a42aa2
Apply suggestions from code review
epheph 7d159f8
Merge remote-tracking branch 'origin/main' into eth_multicall
epheph 7787ce8
Using `errors` style, as seen in more modern api error definitions. S…
epheph d8ca0a4
Merge remote-tracking branch 'df/eth_multicall' into eth_multicall
epheph 90016c0
Update src/schemas/execute.yaml
MicahZoltu d596116
Update src/schemas/execute.yaml
KillariDev 0f8426a
Removes `int` type.
MicahZoltu 6928d24
Typo: errors -> error
MicahZoltu 9528d89
Fixes error object type for CallResultFailure.
MicahZoltu c6a3397
Changes BlockOverrides.number to a uint64.
MicahZoltu b587f7f
Another `errors => error` typo
MicahZoltu 818ef4a
Timestamp to uint64
MicahZoltu 3c7c14e
Timestamp to uint64
MicahZoltu 1814075
Block number to uint64
MicahZoltu c3999c2
Fixes bug in spec (error was both object and list).
MicahZoltu 1a92538
add multicall object
KillariDev 41213eb
fix error unions
KillariDev e785349
add descriptions
KillariDev 024958c
fix account override
KillariDev 385791c
use blockStateCalls instead of of "calls"
KillariDev 557038a
Now builds without errors, but fails lint script / schema checks
0xjimmy e2ae78f
Apply suggestions from code review
MicahZoltu d1cce31
Update src/schemas/execute.yaml
KillariDev dfdab76
move errors to root, add couple standard errors, add more clarifying …
KillariDev 2dde369
clarify statediff/state/moveToAddress params
KillariDev dd0fe15
make StateOverrides a dictionary
KillariDev 6cde1ab
Update execute.yaml
KillariDev 62ab430
update prevRandao, baseFeePerGas and feeRecipient back. Add descripti…
KillariDev bde1830
change `moveToAddress` to `movePrecompileToAddress`
KillariDev 8d8725a
Add suggested default values for transactions
KillariDev dc4017b
fix according to comments
KillariDev a3f89ad
move call invalid errors to top level of multicall
KillariDev 28d995f
multicall notes
KillariDev 50c09b8
update document
KillariDev be26261
Update docs/multicall-notes.md
KillariDev 0630df2
Update docs/multicall-notes.md
KillariDev e861a4a
Update docs/multicall-notes.md
KillariDev cba6d4e
Update docs/multicall-notes.md
KillariDev 2196a29
Update docs/multicall-notes.md
KillariDev 40daadb
Update docs/multicall-notes.md
KillariDev d831c5f
fix according to comments
KillariDev 995acb2
add new error for gas limit and block number limits
KillariDev 88f1f7b
add failures comment
KillariDev 235c40c
Update docs/multicall-notes.md
KillariDev e817b74
Update docs/multicall-notes.md
KillariDev 78b3a92
Update docs/multicall-notes.md
KillariDev cb5d419
Update docs/multicall-notes.md
KillariDev 8d3b4d4
add info about overriding default values and restrictions on them
KillariDev 5f09367
add note
KillariDev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
# Multicall | ||
This document contains some extra information that couldn't be fit to the specification document directly. | ||
|
||
## Default block values | ||
Unlike `eth_call`, `eth_multicallV1`'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, except for phantom blocks, see below Phantom block section | | ||
| parentHash | Previous blocks hash (the real hash, or phantom blocks hash) | | ||
| timestamp | The timestamp of previous block + 1 | | ||
| baseFeePerGas | Calculated on what it should be according to ethereum's spec. Note: baseFeePerGas is not adjusted in the phantom blocks. | | ||
| sha3Uncles | Empty trie root | | ||
| withdrawals | Empty array | | ||
| uncles | Empty array | | ||
| 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 | | ||
|
||
An interesting note here is that we decide timestamp as `previous block timestamp + 1`, while `previous block timestamp + 12` could also be an assumed default. The reasoning to use `+1` is that it's the minimum amount we have to increase the timestamp to keep them valid. While `+12` is what Mainnet uses, there are other chains that use some other values, and we didn't want to complicate the specification to consider all networks. | ||
|
||
### Phantom blocks | ||
The multicall allows you to define on what block number your calls or transactions are being executed on. Eg, consider following call: | ||
```json | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "eth_multicallV1", | ||
"params": [ | ||
{ | ||
"blockStateCalls": [ | ||
{ | ||
"blockOverrides": { | ||
"number": "0x64" | ||
}, | ||
}, | ||
{ | ||
"blockOverrides": { | ||
"number": "0xc8" | ||
}, | ||
} | ||
] | ||
}, | ||
"0xa" | ||
] | ||
} | ||
``` | ||
|
||
Here we want our calls to be executed in blocks 100 (0x64) and in 200 (0xc8). The block numbers can be anything as long as they are increasing and higher than the block we are building from 10 (0xa). Now we end up in a situation where there exists block ranges 13-99 and 101-199 that are not defined anywhere. These blocks are called "phantom blocks". What happens if you try to request block hash of any of such blocks in the EVM? How can we calculate the block hash of future blocks when we don't know the block hash of the previous block? | ||
|
||
Our solution to this problem is to define block hash of a phantom block to be: | ||
|
||
``` | ||
keccak(rlp([hash_of_previous_non_phantom_block, phantom_block_number])) | ||
``` | ||
|
||
So for example in our example, you could get block hash of block 142 as follows: | ||
``` | ||
keccac(rlp([hash of block 12, 142])) | ||
``` | ||
|
||
The phantom blocks other properties are set to their default properties as defined by the multicall specification. We came to this definition by wanting phantom block hashes to be unique if things prior to the phantom block changes, so if tooling is storing block hashes somewhere, they should remain unique if things change in the simulation. | ||
|
||
One other approach to this problem would be to really calculate the real block hashes for all the phantom blocks, but this would make generating blocks far in future really expensive, as to generate 100 phantom blocks, you would need to calculate 100 block hashes that all depend on each other. And in most cases, no one really cares about these blocks. | ||
|
||
Base fee per gas is not adjusted in the phantom blocks, their base fee remains constant. | ||
|
||
## Default values for transactions | ||
As multicall 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 | Defaults to correct nonce | | ||
| to | null | | ||
| from | 0x0000000000000000000000000000000000000000 | | ||
| gas limit | Remaining gas in the current block. This is calculated dynamically one by one for each transaction that is being processed. | | ||
| value | 0x0 | | ||
| input | no data | | ||
| gasPrice | 0x0 | | ||
| maxPriorityFeePerGas | 0x0 | | ||
| maxFeePerGas | 0x0 | | ||
| accessList | empty array | | ||
|
||
## 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` and `baseFeePerGas`: | ||
```json | ||
"blockOverrides": { | ||
"number": "0x14", | ||
"time": "0xc8", | ||
"gasLimit": "0x2e631", | ||
"feeRecipient": "0xc100000000000000000000000000000000000000", | ||
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000001234", | ||
"baseFeePerGas": "0x14" | ||
}, | ||
``` | ||
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, phantom blocks are created to fill the gaps. | ||
|
||
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_multicallV1` The multical 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 0x0. | ||
|
||
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_multicallV1", | ||
"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": "0x0000000000000000000000000000000000000000", | ||
"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": "0x0000000000000000000000000000000000000000", | ||
"topics": [ | ||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", | ||
"0x000000000000000000000000c000000000000000000000000000000000000000", | ||
"0x000000000000000000000000c100000000000000000000000000000000000000" | ||
], | ||
"data": "0x00000000000000000000000000000000000000000000000000000000000003e8", | ||
``` | ||
As can be seen, the sending address is the zero address, `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef` corresponds to signature `Transfer(address,address,uint256)`, `"0x000000000000000000000000c000000000000000000000000000000000000000"` corresponds the sending address and `0x000000000000000000000000c100000000000000000000000000000000000000` is the receiving address. The amount of ETH moved 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 | ||
- Multicalls 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 | ||
The multicall has a feature to enable or disable validation with setting `Validation`, by default, the validation is off, and the multicall mimics `eth_call` with reduced number of checks. Validation enabled mode is intended to give as close as possible simulation of real EVM block creation, except there's no checks for transaction signatures and we also allow one to send a direct transaction from a contract. | ||
|
||
## 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 multicall ends and an error is returned. | ||
|
||
## Version number | ||
The method name for multicall `eth_multicallV1` the intention is that after release of multicall, if new features are wanted the `eth_multicallV1` is kept as it is, and instead `eth_multicallV2` 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 two 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 multicall cannot exceed the global gas limit over its lifespan |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"we also allow one to send a direct transaction from a contract." why do we allow this in the strict mode again? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eth_multicall
wide, not per-transaction, and one may have one transaction in the middle of a sequence of transactions that is meant to simulate some contract action, while the rest of the transactions are normal EOA sourced transactions that you want to fully validate.