The JSON-RPC Method `eth_simulateV1` is now available on multiple networks
The eth_simulateV1 method is live on the Ethereum mainnet following the release of Geth v1.14.9 and Nethermind v1.28.0. It is also supported on networks like Base, Optimism, and Gnosis.What is eth_simulateV1?The method eth_simulateV1 is an enhancement of eth_call, designed to support multiple simultaneous calls and more advanced features. Previously, multicall functionality was achieved through tools such as:Alchemy forkingHardhat forkingFlashbots eth_callBundleMulticall contractsWhile these ...

The Interceptor and Bouquet Now Supports Optimism, Base And Gnosis
The Interceptor (Get for Firefox or Chrome) version 1.0.1 and higher now supports Optimism, Base and Gnosis! Below, I have compiled a list of JSON-RPCs that work with The Interceptor. You can add any of the RPC URLs below to the Interceptor to connect to the corresponding network. Alternatively, you can run your own node or use other RPC providers with eth_simulateV1`support. Adding an RPC URL to the Interceptor will confirm if it is supported. The servers listed below have been tested to wor...

Breaking the $10,000 iO Bounty: My Journey to Crack an Indistinguishability Obfuscation Implementati…
In Devcon in 14th November 2024, Ethereum foundation, Phantom.zone, and 0xPARC launched a 10 000 USD bounty for their practical Indistinguishability Obfuscation implementation. You can read more about this in obfustopia.io. This text provides a brief explanation of Indistinguishability Obfuscation (iO), an overview of the associated bounty, and a description of how I successfully broke it.What is Indistinguishability Obfuscation?Indistinguishability Obfuscation (commonly abbreviated as iO) is...
<100 subscribers
The JSON-RPC Method `eth_simulateV1` is now available on multiple networks
The eth_simulateV1 method is live on the Ethereum mainnet following the release of Geth v1.14.9 and Nethermind v1.28.0. It is also supported on networks like Base, Optimism, and Gnosis.What is eth_simulateV1?The method eth_simulateV1 is an enhancement of eth_call, designed to support multiple simultaneous calls and more advanced features. Previously, multicall functionality was achieved through tools such as:Alchemy forkingHardhat forkingFlashbots eth_callBundleMulticall contractsWhile these ...

The Interceptor and Bouquet Now Supports Optimism, Base And Gnosis
The Interceptor (Get for Firefox or Chrome) version 1.0.1 and higher now supports Optimism, Base and Gnosis! Below, I have compiled a list of JSON-RPCs that work with The Interceptor. You can add any of the RPC URLs below to the Interceptor to connect to the corresponding network. Alternatively, you can run your own node or use other RPC providers with eth_simulateV1`support. Adding an RPC URL to the Interceptor will confirm if it is supported. The servers listed below have been tested to wor...

Breaking the $10,000 iO Bounty: My Journey to Crack an Indistinguishability Obfuscation Implementati…
In Devcon in 14th November 2024, Ethereum foundation, Phantom.zone, and 0xPARC launched a 10 000 USD bounty for their practical Indistinguishability Obfuscation implementation. You can read more about this in obfustopia.io. This text provides a brief explanation of Indistinguishability Obfuscation (iO), an overview of the associated bounty, and a description of how I successfully broke it.What is Indistinguishability Obfuscation?Indistinguishability Obfuscation (commonly abbreviated as iO) is...
Share Dialog
Share Dialog
We have been working with Geth and Nethermind on Execution Spec 484 proposal about eth_multicallV1 to add a new JSON-RPC method to Ethereum main net clients. ETH_multicallV1 was presented in in Execution Layer Meeting 175 and in RPC Standardization meeting in DevConnect Istanbul, slides here. You can also try the most recent multi-call version in live action on main net in The Interceptor browser extension, you can read more about that the extension in The Interceptor - A transaction simulator extension.
Multi calling in the context of Ethereum refers to the capability of executing a series of interdependent calls on the Ethereum state. Each call in this sequence relies on the changes brought about by the preceding one. The essence of multi calling lies in its ability to simulate potential outcomes by considering the inclusion of specific transactions. Additionally, multi calling facilitates the extraction of pertinent data from the blockchain, allowing us to inquire about existing information on the chain. Currently there exists multiple ways to achieve this, such as:
While each of these methods are cool, and have their merits, none of them are standardized. Also with the multi calling contract's, we cannot simulate multiple transactions, just multiple calls from the same contract.
Introducing eth_multicallV1. This new method is a standardized way to do multi calls via calling JSON-RPC directly. The method is aimed to function similar to eth_call but with significantly improved features. You can do everything eth_call can do and more.
On top of simply being able to make multiple calls, eth_multicallV1 enables:
Calls are split to blocks - We can define custom block variables!
State overrides - We can replace code and balance of accounts!
Precompile override - We can replace precompiles with any code!
Event logs & ETH Transfer logs - we can see more on what happens besides of one call result
Validation mode - make the simulation stricter to be more certain what you include is also possible on-chain
In this blog post, we delve into a more detailed exploration of the five new features introduced in multi call.
With the eth_call function, you can execute a single call within a user-defined block. In contrast, with eth_multicallV1, our goal is to empower you to make calls across different blocks and provide the flexibility to override block parameters for each of these calls.
Here's a simple eth_multicallV1 call that showcases on how you can define two blocks 1001 (0x3e9) and 2001 (0x7d1) and put calls inside them:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"blockOverrides": {
"number": "0x3e9",
"time": "0x3e9",
"gasLimit": "0x3ec",
"feeRecipient": "0xc200000000000000000000000000000000000000",
"prevRandao": "0xc300000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": "0x3ef"
},
"calls": [ "eth_calls" ]
},
{
"blockOverrides": {
"number": "0x7d1",
"time": "0x7d1",
"gasLimit": "0x3ec",
"feeRecipient": "0xc200000000000000000000000000000000000000",
"prevRandao": "0xc300000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": "0x3ef"
}
"calls": [ "more eth_calls" ]
}
]
},
"latest"
]
}
Additionally, you can observe that there are six parameters available for override: number, time, gasLimit, feeRecipient, prevRandao, and baseFeePerGas. You have the flexibility to set these values as per your preferences, with the exception of number and time, which must increase block after block. We do not permit time or block numbers to move backward.
A noteworthy aspect of these variables is the allowance for time and blocks to leap forward beyond the usual limits within a chain. For instance, you can define block 10 and subsequently define block 200 000, enabling you to simulate events significantly into the future without the need to define each intermediate block. If you're interested in understanding the implications for blocks 11 to 199,999, read about phantom blocks.
The multi-call method allows us to override balance, code, nonce and state for each account. The state variable can be used to replace the whole state of an account and stateDiff can be used to just add to the existing state.
This account state manipulation enables us to do at least these things:
Set your accounts balance and mint yourself some ETH to use in DEFI
Change the state of a ERC20 smart contract to mint yourself the given token
Replace existing smart contract with new code that bypasses the checks the contract would normally do
"Deploy" a smart contract and call it right away, without actually needing to deploy it on the actual chain
Consider a simple smart contract that can be used to retrieve accounts balance
pragma solidity ^0.8.18;
contract BalanceGetter {
function getBalance(address addr) view external returns (uint256) {
return address(addr).balance;
}
}
Here's a call that sets our balance, deploys the BalanceGetter contract by directly setting some accounts state to contain that code and then calls it to request our balance:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"stateOverrides": {
"0xc000000000000000000000000000000000000000": {
"balance": "0x2710"
},
"0xc200000000000000000000000000000000000000": {
"code": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"
}
},
"calls": [
{
"from": "0xc000000000000000000000000000000000000000",
"to": "0xc200000000000000000000000000000000000000",
"input": "0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000"
},
]
}
]
},
"latest"
]
}
Which then returns the following JSON response:
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"number": "0x11c7689",
"hash": "0x0461546a316760d9fa9c82fe2df4ca478d8b3d7a3a02b609eed2ac663eca7804",
"timestamp": "0x6560c64c",
"gasLimit": "0x1c9c380",
"gasUsed": "0x55b3",
"feeRecipient": "0x6d2e03b7effeae98bd302a9f836d0d6ab0002766",
"baseFeePerGas": "0x9c7b194d4",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"calls": [
{
"returnData": "0x0000000000000000000000000000000000000000000000000000000000002710",
"logs": [],
"gasUsed": "0x55b3",
"status": "0x1"
}
]
}
]
}
As evident from the provided response, it is noteworthy that the verbosity surpasses typical expectations for a singular eth_call. Let's delve into the intricacies of the response. Initially, we observe the return of an array of results (result: []), where each array member represents a block containing the outcomes of the executed calls. As previously explained, multi-call encapsulates calls within locks, and the response includes comprehensive information about both the blocks and the corresponding call results.
Inside a block result, you can see that the there's calls return:
"calls": [
{
"returnData": "0x0000000000000000000000000000000000000000000000000000000000002710",
"logs": [],
"gasUsed": "0x55b3",
"status": "0x1"
}
]
We observe the retrieved returnData, representing our balance in alignment with the expected result from the contract. Additionally, we include the logs in the returned data, although in this particular example, no logs are present. This inclusion of logs marks a notable enhancement over the previous eth_call method, which only returned the old returnData field. Beyond these aspects, we provide the gasUsed field, indicating the amount of gas consumed during the execution of the call. This information proves valuable for gas estimation calculations and comes at negligible cost for nodes, as gas accounting is an integral part of the process.
As precompiles are essentially accounts on the chain, they can be replaced in the same manner as any other account override. However, precompiles come with a unique characteristic—once overridden, there's no direct access to the original precompile in your call. While you could implement your own Solidity version of ecRecover, this approach tends to be gas-inefficient. To address this challenge, we introduce a multi-call setting called movePrecompileToAddress, enabling you to override a precompile and subsequently relocate it to a new account.
A practical use case illustrating the usefulness of overriding ecrecover is the Uniswap Permit2 swap. To execute such a swap, users must sign an off-chain message, which is then included on-chain to validate the user's approval.
Now, let's explore how to simulate a USDC to WBTC swap from Vitalik's account (0xd8da6bf26964af9d7eed9e03e53415d37aa96045) with an ecrecover override, without having access to Vitalik's private key.
Below you can see the contract that we will be using to replace ecrecover. The contract has couple parts
overrideToAddress mapping that will contain ecrecover parameter hashes that we want to override to return an address of our choosing.
A fallback method is invoked when ecrecover is called, checking for any overrides in the overrideToAddress mapping. If an override is present, the method retrieves the associated account. In the absence of an override, the method defaults to calling a contract at address 0x123456, where the actual implementation of ecrecover is relocated.
pragma solidity ^0.8.18;
contract ecRecoverOverride {
mapping(bytes32 => address) overrideToAddress;
fallback (bytes calldata input) external returns (bytes memory) {
(bytes32 hash, uint8 v, bytes32 r, bytes32 s) = abi.decode(input, (bytes32, uint8, bytes32, bytes32));
address overridedAddress = overrideToAddress[keccak256(abi.encode(hash, v, r, s))];
if (overridedAddress == address(0x0)) {
(bool success, bytes memory data) = address(0x0000000000000000000000000000000000123456).call{gas: 10000}(input);
require(success, 'failed to call moved ecrecover at address 0x0000000000000000000000000000000000123456');
return data;
} else {
return abi.encode(overridedAddress);
}
}
}
Then we will be making the following multi-call query. Interesting part about this multi-call is
We are making two calls. The first one is to approve Uniswaps Permit2 contract and the second is us making the actual swap
For the second transaction (the swap itself) to succeed, we have to override ecrecover residing in address 0x1. We replace this address code, move the precompile to address 0x123456 and fill the overrideToAddress mapping with appropriate override.
The eth_call RPC method typically returns only the output corresponding to the return value of the initial function invoked. However, in most cases, contracts interact with users through events distributed at various points during contract execution. In the case of eth_multicallV1, we have incorporated logs into the output to enhance comprehension of the simulation process.
In addition to these logs, we have introduced ERC20-like logs for Ethereum transfers. The logging of ETH movements can be toggled on or off using the traceTransfers flag within the function call.
To illustrate, consider the following example where we override the governance time lock contract of Dope Wars. This allows us to simulate the outcome if a specific governance vote were to succeed. Such simulations can already be conducted using The Interceptor tool.
{
"jsonrpc": "2.0",
"id": 204,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"calls": [
{
"type": "0x2",
"from": "0xdbd38f7e739709fe5bfae6cc8ef67c3820830e0c",
"nonce": "0x0",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"to": "0xb57ab8767cae33be61ff15167134861865f7d22c",
"value": "0x0",
"input": "execute timelock",
"chainId": "0x1",
"accessList": []
}
],
"stateOverrides": {
"0xb57ab8767cae33be61ff15167134861865f7d22c": {
"stateDiff": {},
"code": "Timelock contract replacement bytecode"
}
},
}
],
"traceTransfers": true,
"validation": false
},
"0x11b1f64"
]
}
And here's the full output of the eth_multicallV1:
{
"jsonrpc": "2.0",
"id": 204,
"result": [
{
"number": "0x11b1f65",
"hash": "0x673fb12c793b9b118d6effdd74e9491a04e1666551f19bdb49fa95b9e134acaf",
"timestamp": "0x65509098",
"gasLimit": "0x1c9c380",
"gasUsed": "0xbe97",
"feeRecipient": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
"baseFeePerGas": "0x429978e78",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"calls": [
{
"returnData": "0x",
"logs": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000b57ab8767cae33be61ff15167134861865f7d22c",
"0x000000000000000000000000ced10840f87a2320fdca1dbe17d4f8e4211840a8"
],
"data": "0x0000000000000000000000000000000000000000000000000f43fc2c04ee0000",
"blockNumber": "0x11b1f65",
"transactionHash": "0xdc7f600bef3a06b0864572f85634a4ffa00b8c4318949168727d89b4560b24b0",
"transactionIndex": "0x0",
"blockHash": "0x673fb12c793b9b118d6effdd74e9491a04e1666551f19bdb49fa95b9e134acaf",
"logIndex": "0x0",
"removed": false
},
{
"address": "0xb57ab8767cae33be61ff15167134861865f7d22c",
"topics": [
"0xa560e3198060a2f10670c1ec5b403077ea6ae93ca8de1c32b451dc1a943cd6e7",
"0x3e6eeeeced3a3b85bb1f37bb260f823dca5e1013558c4d93984762be0154ff21",
"0x000000000000000000000000ced10840f87a2320fdca1dbe17d4f8e4211840a8"
],
"data": "0x0000000000000000000000000000000000000000000000000f43fc2c04ee0000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockNumber": "0x11b1f65",
"transactionHash": "0xdc7f600bef3a06b0864572f85634a4ffa00b8c4318949168727d89b4560b24b0",
"transactionIndex": "0x0",
"blockHash": "0x673fb12c793b9b118d6effdd74e9491a04e1666551f19bdb49fa95b9e134acaf",
"logIndex": "0x1",
"removed": false
}
],
"gasUsed": "0xbe97",
"status": "0x1"
}
]
}
]
}
The execution generated two distinct logs. The initial log captures an Ethereum (ETH) transfer event, discernible by its origin address, 0x0, conforming to the ERC20 standard where the first topic denotes the transfer signature, the second pertains to the from field, and the third to the to field. The data field corresponds to the transferred ETH amount, which, in this instance, is 1.1 ETH. The second log documents our interaction with the time lock contract.
Notice the presence of the validation flag in our function calls. When activated, this flag prompts the client to rigorously enforce execution proximity to the ongoing checks. In the context of Geth, enabling validation mode triggers the following checks:
Verification of the correctness of the nonce, ensuring it falls within acceptable limits—neither too high nor too low.
Prevention of nonce overflow.
Calculation of gas prices.
While our execution closely mirrors the blockchain process, we've made a deliberate choice to consistently disable the sender is not EOA check. This decision stems from the considerable utility in allowing calls from contracts.
If you are interested in contributing to eth_multicall development please leave your feedback to the pull request, or by contacting me on twitter @qhuesten for coordination.
We have been working with Geth and Nethermind on Execution Spec 484 proposal about eth_multicallV1 to add a new JSON-RPC method to Ethereum main net clients. ETH_multicallV1 was presented in in Execution Layer Meeting 175 and in RPC Standardization meeting in DevConnect Istanbul, slides here. You can also try the most recent multi-call version in live action on main net in The Interceptor browser extension, you can read more about that the extension in The Interceptor - A transaction simulator extension.
Multi calling in the context of Ethereum refers to the capability of executing a series of interdependent calls on the Ethereum state. Each call in this sequence relies on the changes brought about by the preceding one. The essence of multi calling lies in its ability to simulate potential outcomes by considering the inclusion of specific transactions. Additionally, multi calling facilitates the extraction of pertinent data from the blockchain, allowing us to inquire about existing information on the chain. Currently there exists multiple ways to achieve this, such as:
While each of these methods are cool, and have their merits, none of them are standardized. Also with the multi calling contract's, we cannot simulate multiple transactions, just multiple calls from the same contract.
Introducing eth_multicallV1. This new method is a standardized way to do multi calls via calling JSON-RPC directly. The method is aimed to function similar to eth_call but with significantly improved features. You can do everything eth_call can do and more.
On top of simply being able to make multiple calls, eth_multicallV1 enables:
Calls are split to blocks - We can define custom block variables!
State overrides - We can replace code and balance of accounts!
Precompile override - We can replace precompiles with any code!
Event logs & ETH Transfer logs - we can see more on what happens besides of one call result
Validation mode - make the simulation stricter to be more certain what you include is also possible on-chain
In this blog post, we delve into a more detailed exploration of the five new features introduced in multi call.
With the eth_call function, you can execute a single call within a user-defined block. In contrast, with eth_multicallV1, our goal is to empower you to make calls across different blocks and provide the flexibility to override block parameters for each of these calls.
Here's a simple eth_multicallV1 call that showcases on how you can define two blocks 1001 (0x3e9) and 2001 (0x7d1) and put calls inside them:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"blockOverrides": {
"number": "0x3e9",
"time": "0x3e9",
"gasLimit": "0x3ec",
"feeRecipient": "0xc200000000000000000000000000000000000000",
"prevRandao": "0xc300000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": "0x3ef"
},
"calls": [ "eth_calls" ]
},
{
"blockOverrides": {
"number": "0x7d1",
"time": "0x7d1",
"gasLimit": "0x3ec",
"feeRecipient": "0xc200000000000000000000000000000000000000",
"prevRandao": "0xc300000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": "0x3ef"
}
"calls": [ "more eth_calls" ]
}
]
},
"latest"
]
}
Additionally, you can observe that there are six parameters available for override: number, time, gasLimit, feeRecipient, prevRandao, and baseFeePerGas. You have the flexibility to set these values as per your preferences, with the exception of number and time, which must increase block after block. We do not permit time or block numbers to move backward.
A noteworthy aspect of these variables is the allowance for time and blocks to leap forward beyond the usual limits within a chain. For instance, you can define block 10 and subsequently define block 200 000, enabling you to simulate events significantly into the future without the need to define each intermediate block. If you're interested in understanding the implications for blocks 11 to 199,999, read about phantom blocks.
The multi-call method allows us to override balance, code, nonce and state for each account. The state variable can be used to replace the whole state of an account and stateDiff can be used to just add to the existing state.
This account state manipulation enables us to do at least these things:
Set your accounts balance and mint yourself some ETH to use in DEFI
Change the state of a ERC20 smart contract to mint yourself the given token
Replace existing smart contract with new code that bypasses the checks the contract would normally do
"Deploy" a smart contract and call it right away, without actually needing to deploy it on the actual chain
Consider a simple smart contract that can be used to retrieve accounts balance
pragma solidity ^0.8.18;
contract BalanceGetter {
function getBalance(address addr) view external returns (uint256) {
return address(addr).balance;
}
}
Here's a call that sets our balance, deploys the BalanceGetter contract by directly setting some accounts state to contain that code and then calls it to request our balance:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"stateOverrides": {
"0xc000000000000000000000000000000000000000": {
"balance": "0x2710"
},
"0xc200000000000000000000000000000000000000": {
"code": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"
}
},
"calls": [
{
"from": "0xc000000000000000000000000000000000000000",
"to": "0xc200000000000000000000000000000000000000",
"input": "0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000"
},
]
}
]
},
"latest"
]
}
Which then returns the following JSON response:
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"number": "0x11c7689",
"hash": "0x0461546a316760d9fa9c82fe2df4ca478d8b3d7a3a02b609eed2ac663eca7804",
"timestamp": "0x6560c64c",
"gasLimit": "0x1c9c380",
"gasUsed": "0x55b3",
"feeRecipient": "0x6d2e03b7effeae98bd302a9f836d0d6ab0002766",
"baseFeePerGas": "0x9c7b194d4",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"calls": [
{
"returnData": "0x0000000000000000000000000000000000000000000000000000000000002710",
"logs": [],
"gasUsed": "0x55b3",
"status": "0x1"
}
]
}
]
}
As evident from the provided response, it is noteworthy that the verbosity surpasses typical expectations for a singular eth_call. Let's delve into the intricacies of the response. Initially, we observe the return of an array of results (result: []), where each array member represents a block containing the outcomes of the executed calls. As previously explained, multi-call encapsulates calls within locks, and the response includes comprehensive information about both the blocks and the corresponding call results.
Inside a block result, you can see that the there's calls return:
"calls": [
{
"returnData": "0x0000000000000000000000000000000000000000000000000000000000002710",
"logs": [],
"gasUsed": "0x55b3",
"status": "0x1"
}
]
We observe the retrieved returnData, representing our balance in alignment with the expected result from the contract. Additionally, we include the logs in the returned data, although in this particular example, no logs are present. This inclusion of logs marks a notable enhancement over the previous eth_call method, which only returned the old returnData field. Beyond these aspects, we provide the gasUsed field, indicating the amount of gas consumed during the execution of the call. This information proves valuable for gas estimation calculations and comes at negligible cost for nodes, as gas accounting is an integral part of the process.
As precompiles are essentially accounts on the chain, they can be replaced in the same manner as any other account override. However, precompiles come with a unique characteristic—once overridden, there's no direct access to the original precompile in your call. While you could implement your own Solidity version of ecRecover, this approach tends to be gas-inefficient. To address this challenge, we introduce a multi-call setting called movePrecompileToAddress, enabling you to override a precompile and subsequently relocate it to a new account.
A practical use case illustrating the usefulness of overriding ecrecover is the Uniswap Permit2 swap. To execute such a swap, users must sign an off-chain message, which is then included on-chain to validate the user's approval.
Now, let's explore how to simulate a USDC to WBTC swap from Vitalik's account (0xd8da6bf26964af9d7eed9e03e53415d37aa96045) with an ecrecover override, without having access to Vitalik's private key.
Below you can see the contract that we will be using to replace ecrecover. The contract has couple parts
overrideToAddress mapping that will contain ecrecover parameter hashes that we want to override to return an address of our choosing.
A fallback method is invoked when ecrecover is called, checking for any overrides in the overrideToAddress mapping. If an override is present, the method retrieves the associated account. In the absence of an override, the method defaults to calling a contract at address 0x123456, where the actual implementation of ecrecover is relocated.
pragma solidity ^0.8.18;
contract ecRecoverOverride {
mapping(bytes32 => address) overrideToAddress;
fallback (bytes calldata input) external returns (bytes memory) {
(bytes32 hash, uint8 v, bytes32 r, bytes32 s) = abi.decode(input, (bytes32, uint8, bytes32, bytes32));
address overridedAddress = overrideToAddress[keccak256(abi.encode(hash, v, r, s))];
if (overridedAddress == address(0x0)) {
(bool success, bytes memory data) = address(0x0000000000000000000000000000000000123456).call{gas: 10000}(input);
require(success, 'failed to call moved ecrecover at address 0x0000000000000000000000000000000000123456');
return data;
} else {
return abi.encode(overridedAddress);
}
}
}
Then we will be making the following multi-call query. Interesting part about this multi-call is
We are making two calls. The first one is to approve Uniswaps Permit2 contract and the second is us making the actual swap
For the second transaction (the swap itself) to succeed, we have to override ecrecover residing in address 0x1. We replace this address code, move the precompile to address 0x123456 and fill the overrideToAddress mapping with appropriate override.
The eth_call RPC method typically returns only the output corresponding to the return value of the initial function invoked. However, in most cases, contracts interact with users through events distributed at various points during contract execution. In the case of eth_multicallV1, we have incorporated logs into the output to enhance comprehension of the simulation process.
In addition to these logs, we have introduced ERC20-like logs for Ethereum transfers. The logging of ETH movements can be toggled on or off using the traceTransfers flag within the function call.
To illustrate, consider the following example where we override the governance time lock contract of Dope Wars. This allows us to simulate the outcome if a specific governance vote were to succeed. Such simulations can already be conducted using The Interceptor tool.
{
"jsonrpc": "2.0",
"id": 204,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"calls": [
{
"type": "0x2",
"from": "0xdbd38f7e739709fe5bfae6cc8ef67c3820830e0c",
"nonce": "0x0",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"to": "0xb57ab8767cae33be61ff15167134861865f7d22c",
"value": "0x0",
"input": "execute timelock",
"chainId": "0x1",
"accessList": []
}
],
"stateOverrides": {
"0xb57ab8767cae33be61ff15167134861865f7d22c": {
"stateDiff": {},
"code": "Timelock contract replacement bytecode"
}
},
}
],
"traceTransfers": true,
"validation": false
},
"0x11b1f64"
]
}
And here's the full output of the eth_multicallV1:
{
"jsonrpc": "2.0",
"id": 204,
"result": [
{
"number": "0x11b1f65",
"hash": "0x673fb12c793b9b118d6effdd74e9491a04e1666551f19bdb49fa95b9e134acaf",
"timestamp": "0x65509098",
"gasLimit": "0x1c9c380",
"gasUsed": "0xbe97",
"feeRecipient": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
"baseFeePerGas": "0x429978e78",
"prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
"calls": [
{
"returnData": "0x",
"logs": [
{
"address": "0x0000000000000000000000000000000000000000",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x000000000000000000000000b57ab8767cae33be61ff15167134861865f7d22c",
"0x000000000000000000000000ced10840f87a2320fdca1dbe17d4f8e4211840a8"
],
"data": "0x0000000000000000000000000000000000000000000000000f43fc2c04ee0000",
"blockNumber": "0x11b1f65",
"transactionHash": "0xdc7f600bef3a06b0864572f85634a4ffa00b8c4318949168727d89b4560b24b0",
"transactionIndex": "0x0",
"blockHash": "0x673fb12c793b9b118d6effdd74e9491a04e1666551f19bdb49fa95b9e134acaf",
"logIndex": "0x0",
"removed": false
},
{
"address": "0xb57ab8767cae33be61ff15167134861865f7d22c",
"topics": [
"0xa560e3198060a2f10670c1ec5b403077ea6ae93ca8de1c32b451dc1a943cd6e7",
"0x3e6eeeeced3a3b85bb1f37bb260f823dca5e1013558c4d93984762be0154ff21",
"0x000000000000000000000000ced10840f87a2320fdca1dbe17d4f8e4211840a8"
],
"data": "0x0000000000000000000000000000000000000000000000000f43fc2c04ee0000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockNumber": "0x11b1f65",
"transactionHash": "0xdc7f600bef3a06b0864572f85634a4ffa00b8c4318949168727d89b4560b24b0",
"transactionIndex": "0x0",
"blockHash": "0x673fb12c793b9b118d6effdd74e9491a04e1666551f19bdb49fa95b9e134acaf",
"logIndex": "0x1",
"removed": false
}
],
"gasUsed": "0xbe97",
"status": "0x1"
}
]
}
]
}
The execution generated two distinct logs. The initial log captures an Ethereum (ETH) transfer event, discernible by its origin address, 0x0, conforming to the ERC20 standard where the first topic denotes the transfer signature, the second pertains to the from field, and the third to the to field. The data field corresponds to the transferred ETH amount, which, in this instance, is 1.1 ETH. The second log documents our interaction with the time lock contract.
Notice the presence of the validation flag in our function calls. When activated, this flag prompts the client to rigorously enforce execution proximity to the ongoing checks. In the context of Geth, enabling validation mode triggers the following checks:
Verification of the correctness of the nonce, ensuring it falls within acceptable limits—neither too high nor too low.
Prevention of nonce overflow.
Calculation of gas prices.
While our execution closely mirrors the blockchain process, we've made a deliberate choice to consistently disable the sender is not EOA check. This decision stems from the considerable utility in allowing calls from contracts.
If you are interested in contributing to eth_multicall development please leave your feedback to the pull request, or by contacting me on twitter @qhuesten for coordination.
{
"jsonrpc": "2.0",
"id": 162,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"calls": [
{
"type": "0x2",
"from": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"nonce": "0x481",
"maxFeePerGas": "0x10e2249a2c",
"maxPriorityFeePerGas": "0x5f5e100",
"gas": "0x12631",
"to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"value": "0x0",
"input": "0x095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"chainId": "0x1",
"accessList": []
},
{
"type": "0x2",
"from": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"nonce": "0x482",
"maxFeePerGas": "0x11491519cc",
"maxPriorityFeePerGas": "0x5f5e100",
"gas": "0x424ee",
"to": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"value": "0x0",
"input": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000655f00d400000000000000000000000000000000000000000000000000000000000000030a080c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000658686d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad00000000000000000000000000000000000000000000000000000000655f00e000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041a6b086e6ffec7e22a7cac3d71494f1c7ec44a85c66156aff9fe881bf1fb99bc053dc332293ea7dce14be4cb689d9b75e920b37deab9ed761325999e0b48a66bf1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000042c1d800000000000000000000000000000000000000000000000000072b3980a9ab9fe00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000072b3980a9ab9fe",
"chainId": "0x1",
"accessList": []
}
],
"stateOverrides": {
"0x0000000000000000000000000000000000000001": {
"state": {
"0x010d8fdb5b1199f6ac26d39281e100201200fbc7de5bcb9710c3dfeb475c65f6": "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
},
"code": "0x608060405234801561001057600080fd5b506000366060600080600080868681019061002b9190610238565b935093509350935060008060008686868660405160200161004f94939291906102bd565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610191576000806212345673ffffffffffffffffffffffffffffffffffffffff166127108b8b6040516100fa929190610341565b60006040518083038160008787f1925050503d8060008114610138576040519150601f19603f3d011682016040523d82523d6000602084013e61013d565b606091505b509150915081610182576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161017990610403565b60405180910390fd5b809750505050505050506101b9565b806040516020016101a29190610464565b604051602081830303815290604052955050505050505b915050805190602001f35b600080fd5b6000819050919050565b6101dc816101c9565b81146101e757600080fd5b50565b6000813590506101f9816101d3565b92915050565b600060ff82169050919050565b610215816101ff565b811461022057600080fd5b50565b6000813590506102328161020c565b92915050565b60008060008060808587031215610252576102516101c4565b5b6000610260878288016101ea565b945050602061027187828801610223565b9350506040610282878288016101ea565b9250506060610293878288016101ea565b91505092959194509250565b6102a8816101c9565b82525050565b6102b7816101ff565b82525050565b60006080820190506102d2600083018761029f565b6102df60208301866102ae565b6102ec604083018561029f565b6102f9606083018461029f565b95945050505050565b600081905092915050565b82818337600083830152505050565b60006103288385610302565b935061033583858461030d565b82840190509392505050565b600061034e82848661031c565b91508190509392505050565b600082825260208201905092915050565b7f6661696c656420746f2063616c6c206d6f7665642065637265636f766572206160008201527f742061646472657373203078303030303030303030303030303030303030303060208201527f3030303030303030303030303030313233343536000000000000000000000000604082015250565b60006103ed60548361035a565b91506103f88261036b565b606082019050919050565b6000602082019050818103600083015261041c816103e0565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061044e82610423565b9050919050565b61045e81610443565b82525050565b60006020820190506104796000830184610455565b9291505056fea26469706673582212207ddee236692b0fb014c4a668a714cba393524150b3782202194780d8b923261464736f6c63430008120033",
"movePrecompileToAddress": "0x0000000000000000000000000000000000123456"
}
},
"blockOverride": {
"number": "0x11c507e",
"prevRandao": "0x1",
"time": "0x655ef9fb",
"gasLimit": "0x1c9c380",
"feeRecipient": "0x88c6c46ebf353a52bdbab708c23d0c81daa8134a",
"baseFee": "0x68b59f4cb"
}
}
],
"traceTransfers": true,
"validation": false
},
"0x11c507d"
]
}
{
"jsonrpc": "2.0",
"id": 162,
"method": "eth_multicallV1",
"params": [
{
"blockStateCalls": [
{
"calls": [
{
"type": "0x2",
"from": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"nonce": "0x481",
"maxFeePerGas": "0x10e2249a2c",
"maxPriorityFeePerGas": "0x5f5e100",
"gas": "0x12631",
"to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"value": "0x0",
"input": "0x095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"chainId": "0x1",
"accessList": []
},
{
"type": "0x2",
"from": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"nonce": "0x482",
"maxFeePerGas": "0x11491519cc",
"maxPriorityFeePerGas": "0x5f5e100",
"gas": "0x424ee",
"to": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"value": "0x0",
"input": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000655f00d400000000000000000000000000000000000000000000000000000000000000030a080c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000658686d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad00000000000000000000000000000000000000000000000000000000655f00e000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041a6b086e6ffec7e22a7cac3d71494f1c7ec44a85c66156aff9fe881bf1fb99bc053dc332293ea7dce14be4cb689d9b75e920b37deab9ed761325999e0b48a66bf1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000042c1d800000000000000000000000000000000000000000000000000072b3980a9ab9fe00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000072b3980a9ab9fe",
"chainId": "0x1",
"accessList": []
}
],
"stateOverrides": {
"0x0000000000000000000000000000000000000001": {
"state": {
"0x010d8fdb5b1199f6ac26d39281e100201200fbc7de5bcb9710c3dfeb475c65f6": "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
},
"code": "0x608060405234801561001057600080fd5b506000366060600080600080868681019061002b9190610238565b935093509350935060008060008686868660405160200161004f94939291906102bd565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610191576000806212345673ffffffffffffffffffffffffffffffffffffffff166127108b8b6040516100fa929190610341565b60006040518083038160008787f1925050503d8060008114610138576040519150601f19603f3d011682016040523d82523d6000602084013e61013d565b606091505b509150915081610182576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161017990610403565b60405180910390fd5b809750505050505050506101b9565b806040516020016101a29190610464565b604051602081830303815290604052955050505050505b915050805190602001f35b600080fd5b6000819050919050565b6101dc816101c9565b81146101e757600080fd5b50565b6000813590506101f9816101d3565b92915050565b600060ff82169050919050565b610215816101ff565b811461022057600080fd5b50565b6000813590506102328161020c565b92915050565b60008060008060808587031215610252576102516101c4565b5b6000610260878288016101ea565b945050602061027187828801610223565b9350506040610282878288016101ea565b9250506060610293878288016101ea565b91505092959194509250565b6102a8816101c9565b82525050565b6102b7816101ff565b82525050565b60006080820190506102d2600083018761029f565b6102df60208301866102ae565b6102ec604083018561029f565b6102f9606083018461029f565b95945050505050565b600081905092915050565b82818337600083830152505050565b60006103288385610302565b935061033583858461030d565b82840190509392505050565b600061034e82848661031c565b91508190509392505050565b600082825260208201905092915050565b7f6661696c656420746f2063616c6c206d6f7665642065637265636f766572206160008201527f742061646472657373203078303030303030303030303030303030303030303060208201527f3030303030303030303030303030313233343536000000000000000000000000604082015250565b60006103ed60548361035a565b91506103f88261036b565b606082019050919050565b6000602082019050818103600083015261041c816103e0565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061044e82610423565b9050919050565b61045e81610443565b82525050565b60006020820190506104796000830184610455565b9291505056fea26469706673582212207ddee236692b0fb014c4a668a714cba393524150b3782202194780d8b923261464736f6c63430008120033",
"movePrecompileToAddress": "0x0000000000000000000000000000000000123456"
}
},
"blockOverride": {
"number": "0x11c507e",
"prevRandao": "0x1",
"time": "0x655ef9fb",
"gasLimit": "0x1c9c380",
"feeRecipient": "0x88c6c46ebf353a52bdbab708c23d0c81daa8134a",
"baseFee": "0x68b59f4cb"
}
}
],
"traceTransfers": true,
"validation": false
},
"0x11c507d"
]
}
No comments yet