每个 Virtual Environment 都在 0xc100000000000000000000000000000000000000 上暴露一个预编译合约。从交易内部调用此预编译上的函数可让您的合约直接操纵链上状态、存储槽、余额、nonce 和事件日志,而无需假冒账户或部署辅助代理。
这些作弊码是对 Admin RPC 方法的补充。Admin RPC 方法从交易外部操纵状态(通过 JSON-RPC)。预编译从交易内部操纵状态(通过 Solidity 调用)。当您的设置需要保持原子性、可从 tx 哈希重放,或者要在多个槽上循环执行时,请使用预编译。
将此文件作为 IVnetPrecompile.sol 添加到您的项目中:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IVnetPrecompile {
function setStorageAt(address target, bytes32 slot, bytes32 value) external returns (bool);
function getStorageAt(address target, bytes32 slot) external view returns (bytes32);
function setBalance(address target, uint256 balance) external returns (bool);
function addBalance(address target, uint256 amount) external returns (bool);
function setNonce(address target, uint256 value) external returns (bool);
function emitEvent(address target, bytes calldata data) external returns (bool);
function emitEvent(address target, bytes32 topic1) external returns (bool);
function emitEvent(address target, bytes32 topic1, bytes calldata data) external returns (bool);
function emitEvent(address target, bytes32 topic1, bytes32 topic2) external returns (bool);
function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes calldata data) external returns (bool);
function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3) external returns (bool);
function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes calldata data) external returns (bool);
function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes32 topic4) external returns (bool);
function emitEvent(address target, bytes32 topic1, bytes32 topic2, bytes32 topic3, bytes32 topic4, bytes calldata data) external returns (bool);
}
在您的合约中通过固定地址引用它:
IVnetPrecompile constant VNET = IVnetPrecompile(0xc100000000000000000000000000000000000000);
Storage
| 函数 | 参数 | 返回值 | 描述 |
|---|
setStorageAt | address target, bytes32 slot, bytes32 value | bool | 将 value 写入 target 上的 slot。 |
getStorageAt | address target, bytes32 slot | bytes32 | 读取 target 上 slot 的当前值。 |
VNET.setStorageAt(tokenContract, balanceSlot, bytes32(uint256(1000e18)));
bytes32 val = VNET.getStorageAt(tokenContract, balanceSlot);
Balance
| 函数 | 参数 | 返回值 | 描述 |
|---|
setBalance | address target, uint256 balance | bool | 将 target 的 ETH 余额设置为精确值。 |
addBalance | address target, uint256 amount | bool | 向 target 的当前 ETH 余额添加 amount。 |
VNET.setBalance(account, 10 ether);
VNET.addBalance(account, 2 ether);
Nonce
| 函数 | 参数 | 返回值 | 描述 |
|---|
setNonce | address target, uint256 value | bool | 设置 target 的交易计数。 |
VNET.setNonce(account, 42);
Events
emitEvent 触发一条 address 字段为 target(而不是调用合约)的日志。下游消费者(索引器、subgraph、webhook)会将该日志视为来自 target 的真实发出。
九个重载覆盖了从零到四个索引 topic,以及可选的未索引 data 载荷:
| 重载 | Topics | Data |
|---|
emitEvent(target, data) | 0 | ✓ |
emitEvent(target, t1) | 1 | , |
emitEvent(target, t1, data) | 1 | ✓ |
emitEvent(target, t1, t2) | 2 | , |
emitEvent(target, t1, t2, data) | 2 | ✓ |
emitEvent(target, t1, t2, t3) | 3 | , |
emitEvent(target, t1, t2, t3, data) | 3 | ✓ |
emitEvent(target, t1, t2, t3, t4) | 4 | , |
emitEvent(target, t1, t2, t3, t4, data) | 4 | ✓ |
topic1 通常是 keccak256("EventName(type,type,...)")。示例,伪造一次 USDC 的 Transfer:
VNET.emitEvent(
USDC,
keccak256("Transfer(address,address,uint256)"),
bytes32(uint256(uint160(from))),
bytes32(uint256(uint160(to))),
abi.encode(amount)
);
预编译还会发出它自己的诊断日志(余额设置、nonce 设置、存储写入),其 address = 0xc100000000000000000000000000000000000000。检查伪造日志的收据时,请按目标地址过滤 logs[] 以忽略预编译自身的条目。
已知限制
- 基于 Arbitrum 的 Virtual Environments。 由于 Arbitrum 内部机制,通过
eth_call 进行的作弊码调用无法工作。交易、gas 估算和模拟按预期工作。
另请参阅