跳转到主要内容
每个 Virtual Environment 都在 0xc100000000000000000000000000000000000000 上暴露一个预编译合约。从交易内部调用此预编译上的函数可让您的合约直接操纵链上状态、存储槽、余额、nonce 和事件日志,而无需假冒账户或部署辅助代理。 这些作弊码是对 Admin RPC 方法的补充。Admin RPC 方法从交易外部操纵状态(通过 JSON-RPC)。预编译从交易内部操纵状态(通过 Solidity 调用)。当您的设置需要保持原子性、可从 tx 哈希重放,或者要在多个槽上循环执行时,请使用预编译。

接口

将此文件作为 IVnetPrecompile.sol 添加到您的项目中:
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

函数参数返回值描述
setStorageAtaddress target, bytes32 slot, bytes32 valueboolvalue 写入 target 上的 slot
getStorageAtaddress target, bytes32 slotbytes32读取 targetslot 的当前值。
VNET.setStorageAt(tokenContract, balanceSlot, bytes32(uint256(1000e18)));
bytes32 val = VNET.getStorageAt(tokenContract, balanceSlot);

Balance

函数参数返回值描述
setBalanceaddress target, uint256 balancebooltarget 的 ETH 余额设置为精确值。
addBalanceaddress target, uint256 amountbooltarget 的当前 ETH 余额添加 amount
VNET.setBalance(account, 10 ether);
VNET.addBalance(account, 2 ether);

Nonce

函数参数返回值描述
setNonceaddress target, uint256 valuebool设置 target 的交易计数。
VNET.setNonce(account, 42);

Events

emitEvent 触发一条 address 字段为 target(而不是调用合约)的日志。下游消费者(索引器、subgraph、webhook)会将该日志视为来自 target 的真实发出。 九个重载覆盖了从零到四个索引 topic,以及可选的未索引 data 载荷:
重载TopicsData
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 估算和模拟按预期工作。

另请参阅