3分钟Solidity: 14.10 Foundry存储测试

31 阅读1分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

Foundry vm.store

使用 vm.store在测试期间直接写入合约的存储槽。

这适用于以下场景:

  • 无需调用合约函数即可设置测试场景
  • 绕过访问控制以测试特定状态
  • 测试正常情况下难以触发的边缘情况

对于映射类型,可通过 keccak256(abi.encode(key, mappingSlot))计算存储槽位置。

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Test, console2} from "forge-std/Test.sol";

contract Vault {
    // Slot 0
    address public owner;
    // Slot 1
    uint256 public password;
    // Slot 2
    mapping(address => uint256) public balances;

    constructor(uint256 _password) {
        owner = msg.sender;
        password = _password;
    }

    function withdraw() external {
        require(msg.sender == owner, "not owner");
        uint256 bal = balances[msg.sender];
        balances[msg.sender] = 0;
        payable(msg.sender).transfer(bal);
    }
}

contract StoreTest is Test {
    Vault vault;

    function setUp() public {
        vault = new Vault(12345);
    }

    // vm.store(address account, bytes32 slot, bytes32 value)
    // - account: 合约地址
    // - slot: 要写入的存储槽
    // - value: 要写入的值

    function test_store_simple_slot() public {
        // 插槽0存储所有者地址
        // 将所有者更改为address(1)
        vm.store(address(vault), bytes32(uint256(0)), bytes32(uint256(uint160(address(1)))));
        assertEq(vault.owner(), address(1));

        // 插槽1存储密码
        // 将密码更改为999
        vm.store(address(vault), bytes32(uint256(1)), bytes32(uint256(999)));
        assertEq(vault.password(), 999);
    }

    function test_store_mapping() public {
        // 对于映射关系,槽位的计算方式为:
        // keccak256(abi.encode(key, mapping_slot))

        address user = address(0xBEEF);
        uint256 mappingSlot = 2; // balances is at slot 2

        // 计算balances[user]的存储槽
        bytes32 slot = keccak256(abi.encode(user, mappingSlot));

        // 将用户余额设置为100以太币
        vm.store(address(vault), slot, bytes32(uint256(100 ether)));

        assertEq(vault.balances(user), 100 ether);
    }
}

Try on Remix