支持 multicall 的JSON-RPC 方法 eth_simulateV1 在多网络可用
- 原文链接:mirror.xyz/killaridev.e...
- 译者:AI翻译官,校对:翻译小组
- 本文链接:learnblockchain.cn/article…
eth_simulateV1
方法在发布 Geth v1.14.9 和 Nethermind v1.28.0 后已经在以太坊主网上线。它也支持 Base、Optimism 和 Gnosis 等网络。
eth_simulateV1
方法是 eth_call
的增强版,旨在支持multicall和更高级的功能。以前,multicall功能是通过以下工具实现的:
虽然这些方法各有优点,但缺乏标准化。此外,multicall合约仅限于在同一合约内进行多次调用,无法模拟多个交易。
相比之下,eth_simulateV1
提供了通过 JSON-RPC 进行multicall的标准化方法( 执行规范 484)。它保留了 eth_call
的核心功能,同时引入了显著的改进。
eth_simulateV1
的主要特性跨区块调用: 定义自定义区块变量以进行细粒度模拟。
状态覆盖(Overrides): 在模拟过程中修改账户余额或替换合约代码。
预编译重载(Overrides): 用自定义代码替代预编译合约。
增强的日志记录: 访问详细的事件和 ETH 转账日志,超出单次调用结果。
验证模式: 强制施加更严格的规则,以确保链上兼容性。
这些功能为开发者提供了更多的权力和灵活性,使得 eth_simulateV1
成为高级交易模拟的强大工具。
在这篇博客中,我们将深入探讨这些新功能,并展示它们在改善以太坊开发工作流程中的潜力。
使用 eth_call
函数,你可以在用户自定义的区块内执行单个调用。相比之下,通过 eth_simulateV1
, 提供跨不同区块进行调用的能力,并提供对每个调用的区块参数进行重载的灵活性。
以下是一个简单的 eth_simulateV1
调用示例,展示了如何定义两个区块 1001
(0x3e9
)和 2001
(0x7d1
)并在其内部放置调用:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_simulateV1",
"params": [
{
"blockStateCalls": [
{
"blockOverrides": {
"number": "0x3e9",
"time": "0x3e9",
"gasLimit": "0x3ec",
"feeRecipient": "0xc200000000000000000000000000000000000000",
"prevRandao": "0xc300000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": "0x3ef",
"blobBaseFee": "0x15"
},
"calls": [ "eth_calls" ]
},
{
"blockOverrides": {
"number": "0x7d1",
"time": "0x7d1",
"gasLimit": "0x3ec",
"feeRecipient": "0xc200000000000000000000000000000000000000",
"prevRandao": "0xc300000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": "0x3ef",
"blobBaseFee": "0x15"
},
"calls": [ "more eth_calls" ]
}
]
},
"latest"
]
}
在定义区块时,你可以重载(Overrides)七个参数:number
、time
、gasLimit
、feeRecipient
、prevRandao
、baseFeePerGas
和blobBaseFee
。所有这些参数,包括 number
和 time
,都可以自定义。然而,number
必须始终递增,而 time
必须递增或保持不变。
这些变量的一个有趣特点是,能够跳过时间或区块,例如,你可以定义区块 10
,然后直接跳到区块 20
。这允许你在不指定每个中间区块的情况下模拟未来事件。在使用 eth_simulateV1
方法时,区块 10
和 20
之间的所有区块都会被模拟并返回。
请注意,单次调用中可创建的区块数有最大限制。默认情况下,该限制为 256
个区块,尽管可以根据客户端进行调整。
此外,还对模拟的总 gas 消耗和返回有效载荷的大小设定了限制。这些限制有助于管理单个 eth_simulateV1
调用对节点造成的负载。gas 和有效载荷大小限制也可以根据客户端自定义。
状态覆盖允许你修改任何账户的 balance
、code
、nonce
和 state
。state
变量可以替换一个账户的整个状态,而 stateDiff
允许你通过逐步应用更改来增强现有状态。
这些强大的账户状态操控工具提供以下能力:
设置账户余额: 调整任何账户的余额,从而有效地铸造 ETH 以用于 DeFi 应用。
代币铸造: 修改 ERC20 智能合约的状态,直接向你的账户铸造代币。
智能合约替换: 替换现有智能合约的代码,以绕过其典型的检查或限制。
即时合约部署: 模拟智能合约的部署并立即与之互动,无需实际在链上部署。
考虑一个简单的智能合约,用于检索账户余额:
pragma solidity 0.8.18;
contract BalanceGetter {
function getBalance(address addr) view external returns (uint256) {
return address(addr).balance;
}
}
以下是一个调用,它设置我们的余额,通过直接设置一些账户状态来部署 BalanceGetter
合约,并调用它以请求我们的余额:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_simulateV1",
"params": [
{
"blockStateCalls": [
{
"stateOverrides": {
"0xc000000000000000000000000000000000000000": {
"balance": "0x2710"
},
"0xc200000000000000000000000000000000000000": {
"code": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"
}
},
"calls": [
{
"from": "0xc000000000000000000000000000000000000000",
"to": "0xc200000000000000000000000000000000000000",
"input": "0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000"
}
]
}
]
},
"latest"
]
}
然后返回以下 JSON 响应:
{
"jsonrpc": "2.0",
"id": 1,
"result": [
{
"baseFeePerGas": "0x0",
"blobGasUsed": "0x0",
"calls": [
{
"returnData": "0x0000000000000000000000000000000000000000000000000000000000002710",
"logs": [],
"gasUsed": "0x55b3",
"status": "0x1"
}
],
"difficulty": "0x0",
"excessBlobGas": "0x3220000",
"extraData": "0x",
"gasLimit": "0x1c9c380",
"gasUsed": "0x55b3",
"hash": "0x4342f0ab870175757176d5888e82f27523a6e1d52c0ac20a6ddf599d54ce0e04",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0000000000000000",
"number": "0x1455a09",
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0xb30e288a5518544cc71dd24389a21061adab20f45f17c0907054dccf7bf00c01",
"receiptsRoot": "0x4ce78e593fcb88a20d7bb31c27879f800551f4069371e576f7efad0d9615a960",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size": "0x29b",
"stateRoot": "0xcfb988bd139a3f44aa79f9cbaba606429d76749fd18e1e8ca9a6612a4e0c8384",
"timestamp": "0x674f0627",
"totalDifficulty": "0xc70d815d562d3cfa955",
"transactions": [
"0x96c6b7b5b4835fd518fd914feb586452cc797d332f1bd234f72c9e692c4c427a"
],
"transactionsRoot": "0xebe30ac336ac6714af65c684e278799f70965b18ae12e9b368056640ac111650",
"uncles": [],
"withdrawals": [],
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
}
]
}
这个响应的详细程度显著高于标准的 eth_call
。它包含一个结果数组(result: []
),其中每个元素代表一个区块,包含了执行调用的结果。如前所述,eth_simulateV1
将调用组织成区块,提供有关区块及其相应调用结果的全面信息。本质上,eth_simulateV1
返回的是你从 eth_getBlockByNumber
获取的模拟区块数据。然而,由于这些区块仅在 eth_simulateV1
调用期间存在,因此此信息直接包含在响应中。
在区块结果中,还返回有关调用执行的信息:
"calls": [
{
"returnData": "0x0000000000000000000000000000000000000000000000000000000000002710",
"logs": [],
"gasUsed": "0x55b3",
"status": "0x1"
}
]
returnData
表示我们的余额,与合约的预期结果一致。此外,我们还包含了返回数据中的日志,尽管在此示例中没有日志。日志的包含标志着相较于先前 eth_call
方法的显著增强,后者仅返回了 returnData
字段。除此之外,我们提供了 gasUsed
字段,指示调用执行期间消耗的 gas 量。这些信息对 gas 估算计算非常有价值,并且对节点而言成本微乎其微,因为 gas 计算是过程的一部分。
由于预编译本质上是链上的账户,因此可以按照与其他账户重写相同的方式进行替换。然而,预编译具有一个独特的特点——一旦被覆盖,就无法直接在调用中访问原始预编译。虽然可以实现你自己的 Solidity 版本的 ecrecover
,但这种方法往往会导致 gas 效率低下。为了解决这个问题,我们引入了一个名为 movePrecompileToAddress
的 eth_simulateV1
设置,使你能够覆盖预编译并随后将其移动到一个新账户。
一个实用的应用场景是覆盖 ecrecover
以执行 Uniswap Permit2 兑换。要执行这样的兑换,用户必须签名一条链外消息,然后将其包含在链上以验证用户的批准。
现在,让我们探讨如何模拟 Vitalik 账户(0xd8da6bf26964af9d7eed9e03e53415d37aa96045
)的 USDC 到 WBTC 的兑换,并覆盖 ecrecover
,而不需要访问 Vitalik 的私钥。
下面是我们将用来替换 ecrecover
的合约。合约分为几个部分:
overrideToAddress
映射将包含我们希望覆盖以返回所选地址的 ecrecover
参数哈希。
一个 fallback 方法在调用 ecrecover
时被调用,查看 overrideToAddress
映射中是否有任何覆盖。如果存在覆盖,方法会检索相关的账户。如果没有覆盖,方法将默认调用地址为 0x123456
的合约,实际的 ecrecover
实现会被迁移到该地址。
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);
}
}
}
一旦合约编写完成,我们可以创建 eth_simulateV1
查询。这涉及两个调用:第一个是 授权 Uniswap 的 Permit2 合约,第二个是执行实际的兑换。为了使兑换成功,我们需要覆盖位于地址 0x1
的 ecrecover
。这需要替换该地址的代码,将预编译移动到地址 0x123456
,并用适当的覆盖更新 overrideToAddress
映射。
{
"jsonrpc": "2.0",
"id": 162,
"method": "eth_simulateV1",
"params": [
{
"blockStateCalls": [
{
"calls": [
{ // approve
"type": "0x2",
"from": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"nonce": "0x481",
"maxFeePerGas": "0x10e2249a2c",
"maxPriorityFeePerGas": "0x5f5e100",
"gas": "0x12631",
"to": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"value": "0x0",
"input": "0x095ea7b3000000000000000000000000000000000022d473030f116ddee9f6b43ac78ba3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"chainId": "0x1",
"accessList": []
},
{ // swap
"type": "0x2",
"from": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
"nonce": "0x482",
"maxFeePerGas": "0x11491519cc",
"maxPriorityFeePerGas": "0x5f5e100",
"gas": "0x424ee",
"to": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad",
"value": "0x0",
"input": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000655f00d400000000000000000000000000000000000000000000000000000000000000030a080c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000ffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000658686d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fc91a3afd70395cd496c647d5a6cc9d4b2b7fad00000000000000000000000000000000000000000000000000000000655f00e000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000041a6b086e6ffec7e22a7cac3d71494f1c7ec44a85c66156aff9fe881bf1fb99bc053dc332293ea7dce14be4cb689d9b75e920b37deab9ed761325999e0b48a66bf1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000042c1d800000000000000000000000000000000000000000000000000072b3980a9ab9fe00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000072b3980a9ab9fe",
"chainId": "0x1",
"accessList": []
}
],
"stateOverrides": {
"0x0000000000000000000000000000000000000001": {
"state": { // override the mapping
"0x010d8fdb5b1199f6ac26d39281e100201200fbc7de5bcb9710c3dfeb475c65f6": "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
},
// replace the ecrecover code with our own code
"code": "0x608060405234801561001057600080fd5b506000366060600080600080868681019061002b9190610238565b935093509350935060008060008686868660405160200161004f94939291906102bd565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610191576000806212345673ffffffffffffffffffffffffffffffffffffffff166127108b8b6040516100fa929190610341565b60006040518083038160008787f1925050503d8060008114610138576040519150601f19603f3d011682016040523d82523d6000602084013e61013d565b606091505b509150915081610182576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161017990610403565b60405180910390fd5b809750505050505050506101b9565b806040516020016101a29190610464565b604051602081830303815290604052955050505050505b915050805190602001f35b600080fd5b6000819050919050565b6101dc816101c9565b81146101e757600080fd5b50565b6000813590506101f9816101d3565b92915050565b600060ff82169050919050565b610215816101ff565b811461022057600080fd5b50565b6000813590506102328161020c565b92915050565b60008060008060808587031215610252576102516101c4565b5b6000610260878288016101ea565b945050602061027187828801610223565b9350506040610282878288016101ea565b9250506060610293878288016101ea565b91505092959194509250565b6102a8816101c9565b82525050565b6102b7816101ff565b82525050565b60006080820190506102d2600083018761029f565b6102df60208301866102ae565b6102ec604083018561029f565b6102f9606083018461029f565b95945050505050565b600081905092915050565b82818337600083830152505050565b60006103288385610302565b935061033583858461030d565b82840190509392505050565b600061034e82848661031c565b91508190509392505050565b600082825260208201905092915050565b7f6661696c656420746f2063616c6c206d6f7665642065637265636f766572206160008201527f742061646472657373203078303030303030303030303030303030303030303060208201527f3030303030303030303030303030313233343536000000000000000000000000604082015250565b60006103ed60548361035a565b91506103f88261036b565b606082019050919050565b6000602082019050818103600083015261041c816103e0565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061044e82610423565b9050919050565b61045e81610443565b82525050565b60006020820190506104796000830184610455565b9291505056fea26469706673582212207ddee236692b0fb014c4a668a714cba393524150b3782202194780d8b923261464736f6c63430008120033",
"movePrecompileToAddress": "0x0000000000000000000000000000000000123456"
}
},
"blockOverride": {
"number": "0x11c507e",
"prevRandao": "0x1",
"time": "0x655ef9fb",
"gasLimit": "0x1c9c380",
"feeRecipient": "0x88c6c46ebf353a52bdbab708c23d0c81daa8134a",
"baseFee": "0x68b59f4cb"
}
}
],
"traceTransfers": true,
"validation": false
},
"0x11c507d"
]
}
eth_call
RPC 方法仅返回与第一个调用的返回值对应的输出。然而,在许多情况下,合约执行各个阶段发出的事件提供了更深入的洞察,以了解发生了什么。为了解决这个问题,eth_simulateV1
在其输出中包含日志,提供了更全面的执行视图。
除了这些日志,eth_simulateV1
还支持类似 ERC20 的以太坊转账日志。ETH 转账的日志可以通过 traceTransfers
标志进行开关控制。
例如,通过覆盖 Dope Wars 的治理时间锁合约,我们可以模拟特定治理投票的结果。这种模拟已经可以使用 The Interceptor 工具进行。
{
"jsonrpc": "2.0",
"id": 204,
"method": "eth_simulateV1",
"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"
]
}
以下是 eth_simulateV1
的调用结果:
{
"returnData": "0x",
"logs": [
{
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"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"
}
执行生成了两个不同的日志。第一个日志捕获了一个以太坊 (ETH) 转账事件,可以通过其来源地址 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
辨别,符合 ERC20 标准,其中第一个主题表示转账签名,第二个与 from
字段相关,第三个与 to
字段相关。data
字段对应于转账的 ETH 数量,在此情况下为 1.1
ETH。第二个日志记录了我们与时间锁合约的交互。
eth_simulateV1
的最后一个重要特性是验证标志。默认情况下,验证是关闭的,eth_simulateV1
的行为类似于 eth_call
,例如,不检查 nonce,gas 是免费的等。当启用该标志时,客户端将把交易视为实际包含在链上。然而,总是会跳过两个检查:
你可以从合约发送 交易(sender is not EOA
检查被禁用)
你不需要为交易提供正确的签名字段。这使你能够在没有访问其私钥的情况下伪造任何账户
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!