Rust分叉数据库管理库foundry-fork-db的使用,实现高效链上数据模拟与测试环境搭建

Rust分叉数据库管理库foundry-fork-db的使用,实现高效链上数据模拟与测试环境搭建

基本信息

  • 版本: 0.16.0
  • 许可证: MIT OR Apache-2.0
  • 大小: 62.9 KiB
  • 最低Rust版本: v1.85.0

安装方式

在项目目录中运行以下Cargo命令:

cargo add foundry-fork-db

或在Cargo.toml中添加:

foundry-fork-db = "0.16.0"

示例代码

以下是一个使用foundry-fork-db创建分叉数据库并模拟链上数据的完整示例:

use foundry_fork_db::{
    fork::{Fork, ForkConfig},
    opts::EvmOpts,
    snapshot::Snapshots,
};
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. 配置分叉设置
    let evm_opts = EvmOpts {
        // 使用以太坊主网作为分叉源
        fork_url: Some("https://mainnet.infura.io/v3/YOUR_API_KEY".to_string()),
        // 从特定区块号分叉
        fork_block_number: Some(15_000_000),
        ..Default::default()
    };

    // 2. 创建分叉配置
    let fork_config = ForkConfig {
        // 缓存目录用于存储分叉数据
        cache_path: Some("./fork_cache".into()),
        // 禁用持久化缓存
        disable_cache: false,
        // 设置请求超时
        timeout: Some(std::time::Duration::from_secs(60)),
        // 使用配置的EvmOpts
        evm_opts,
    };

    // 3. 创建分叉实例
    let fork = Fork::new(fork_config)?;
    let fork = Arc::new(fork);

    // 4. 创建快照管理器
    let snapshots = Snapshots::new(fork.clone());

    // 5. 创建一个新的快照
    let snapshot_id = snapshots.create_snapshot().await?;
    println!("Created snapshot with ID: {:?}", snapshot_id);

    // 6. 模拟链上操作
    // 这里可以执行合约调用、交易等操作
    // 操作只会影响当前快照

    // 7. 恢复到之前的状态
    snapshots.revert_snapshot(snapshot_id).await?;
    println!("Reverted to snapshot: {:?}", snapshot_id);

    // 8. 创建另一个快照用于测试
    let test_snapshot = snapshots.create_snapshot().await?;
    println!("Created test snapshot with ID: {:?}", test_snapshot);

    Ok(())
}

完整示例demo

以下是一个更完整的示例,展示了如何在实际测试中使用foundry-fork-db:

use foundry_fork_db::{
    fork::{Fork, ForkConfig},
    opts::EvmOpts,
    snapshot::Snapshots,
};
use ethers::{
    providers::{Provider, Http},
    types::{Address, U256},
    utils,
};
use std::{sync::Arc, str::FromStr};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. 配置分叉设置
    let evm_opts = EvmOpts {
        fork_url: Some("https://mainnet.infura.io/v3/YOUR_API_KEY".to_string()),
        fork_block_number: Some(15_000_000),
        ..Default::default()
    };

    // 2. 创建分叉配置
    let fork_config = ForkConfig {
        cache_path: Some("./fork_cache".into()),
        disable_cache: false,
        timeout: Some(std::time::Duration::from_secs(60)),
        evm_opts,
    };

    // 3. 创建分叉实例
    let fork = Fork::new(fork_config)?;
    let fork = Arc::new(fork);

    // 4. 创建快照管理器
    let snapshots = Snapshots::new(fork.clone());

    // 5. 创建初始快照
    let initial_snapshot = snapshots.create_snapshot().await?;
    println!("初始快照ID: {:?}", initial_snapshot);

    // 6. 连接到分叉的Provider
    let provider = Provider::new(Http::from_str(&fork.endpoint())?);
    
    // 7. 查询某个地址的ETH余额
    let vitalik_address: Address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse()?;
    let balance = provider.get_balance(vitalik_address, None).await?;
    println!("Vitalik地址余额: {}", utils::format_units(balance, "ether")?);

    // 8. 创建测试快照
    let test_snapshot = snapshots.create_snapshot().await?;
    println!("测试快照ID: {:?}", test_snapshot);

    // 9. 执行测试交易
    // 这里可以添加实际的合约调用或交易测试代码

    // 10. 恢复到初始状态
    snapshots.revert_snapshot(initial_snapshot).await?;
    println!("已恢复到初始快照");

    // 11. 验证余额是否恢复
    let balance_after_revert = provider.get_balance(vitalik_address, None).await?;
    println!("恢复后余额: {}", utils::format_units(balance_after_revert, "ether")?);

    Ok(())
}

主要功能

  1. 分叉管理: 可以从任意以太坊兼容链分叉状态
  2. 快照系统: 创建和恢复状态快照,方便测试不同场景
  3. 缓存支持: 本地缓存分叉数据,提高测试效率
  4. 可配置性: 支持设置分叉区块号、RPC URL等参数

使用场景

  • 智能合约测试
  • 模拟链上交互
  • 开发环境搭建
  • 复杂的多步骤交易测试

注意事项

  • 需要提供有效的RPC端点URL
  • 分叉大型区块可能需要较多内存和存储空间
  • 频繁创建快照可能影响性能

1 回复

Rust分叉数据库管理库foundry-fork-db的使用指南

概述

foundry-fork-db 是一个用于以太坊开发的高效链上数据模拟与测试环境搭建的Rust库,它是Foundry工具链的一部分。该库允许开发者在本地创建分叉(fork)的以太坊环境,模拟主网或测试网状态,便于进行合约测试和开发。

主要特性

  • 支持从任意以太坊网络(主网、测试网)创建分叉
  • 提供内存数据库实现,快速模拟链上状态
  • 支持自定义区块号和链ID
  • 与Foundry工具链无缝集成
  • 高性能的链上数据缓存机制

安装方法

在Cargo.toml中添加依赖:

[dependencies]
foundry-fork-db = "0.1.0"  # 请使用最新版本

基本使用方法

1. 创建分叉数据库

use foundry_fork_db::{
    fork_db::ForkDb,
    fork_factory::ForkFactory,
};

#[tokio::main]
async fn main() {
    // 从以太坊主网在区块号15000000创建分叉
    let fork_url = "https://mainnet.infura.io/v3/YOUR_INFURA_KEY";
    let fork_block_number = Some(15000000);
    
    let fork_factory = ForkFactory::new(fork_url);
    let fork_db = fork_factory.create_fork(fork_block_number).await.unwrap();
    
    println!("Fork created at block {}", fork_db.block_number());
}

2. 查询分叉状态

use ethers::types::Address;

// 继续上面的代码
let dai_address: Address = "0x6B175474E89094C44Da98b954EedeAC495271d0F".parse().unwrap();
let dai_balance = fork_db.get_balance(dai_address).await.unwrap();

println!("DAI合约在分叉区块的余额: {}", dai_balance);

3. 修改分叉状态

use ethers::types::{Address, U256};

let user_address: Address = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B".parse().unwrap();

// 给用户地址充值100ETH
fork_db.set_balance(user_address, U256::from(100) * U256::exp10(18)).unwrap();

// 验证余额
let new_balance = fork_db.get_balance(user_address).await.unwrap();
println!("用户新余额: {} ETH", new_balance);

高级用法

1. 与测试框架集成

use foundry_fork_db::fork_factory::ForkFactory;
use foundry_evm::executor::Executor;

#[tokio::test]
async fn test_contract_on_fork() {
    let fork_factory = ForkFactory::new("https://mainnet.infura.io/v3/YOUR_KEY");
    let fork_db = fork_factory.create_fork(None).await.unwrap();
    
    let mut executor = Executor::new(fork_db);
    
    // 在这里部署和测试合约
    // ...
}

2. 多分叉管理

use foundry_fork_db::fork_factory::ForkFactory;

#[tokio::main]
async fn main() {
    let fork_factory = ForkFactory::new("https://mainnet.infura.io/v3/YOUR_KEY");
    
    // 创建两个不同区块高度的分叉
    let fork_db1 = fork_factory.create_fork(Some(15000000)).await.unwrap();
    let fork_db2 = fork_factory.create_fork(Some(16000000)).await.unwrap();
    
    // 比较两个分叉状态下某个合约的差异
    // ...
}

性能优化技巧

  1. 使用本地缓存:配置分叉数据库使用本地缓存可以显著提高性能

    let fork_factory = ForkFactory::new("https://mainnet.infura.io/v3/YOUR_KEY")
        .with_cache_dir("/path/to/cache");
    
  2. 限制获取的状态:只获取测试需要的状态数据

    let fork_db = fork_factory.create_fork_with_state(
        Some(15000000),
        vec![address1, address2]  // 只获取这些地址的状态
    ).await.unwrap();
    
  3. 批量操作:使用批量查询和写入方法减少RPC调用

注意事项

  1. 需要稳定的RPC节点连接,推荐使用Infura或Alchemy等服务
  2. 分叉数据库会占用较多内存,特别是模拟大区块时
  3. 测试完成后应清理缓存文件,特别是运行大量测试时

foundry-fork-db为以太坊智能合约开发提供了强大的测试环境模拟能力,可以显著提高开发效率和测试覆盖率。

完整示例demo

下面是一个完整的foundry-fork-db使用示例,展示了创建分叉、查询状态和修改状态的全流程:

use foundry_fork_db::{fork_db::ForkDb, fork_factory::ForkFactory};
use ethers::types::{Address, U256};
use std::str::FromStr;

#[tokio::main]
async fn main() {
    // 1. 创建分叉数据库
    let fork_url = "https://mainnet.infura.io/v3/YOUR_INFURA_KEY";
    let fork_block_number = Some(15000000);
    
    let fork_factory = ForkFactory::new(fork_url)
        .with_cache_dir("/tmp/fork_cache"); // 启用本地缓存
    
    println!("正在创建分叉...");
    let fork_db = fork_factory.create_fork(fork_block_number).await.unwrap();
    println!("成功创建分叉,区块高度: {}", fork_db.block_number());

    // 2. 查询状态
    let dai_address = Address::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap();
    let dai_balance = fork_db.get_balance(dai_address).await.unwrap();
    println!("DAI合约余额: {}", dai_balance);

    // 3. 修改状态
    let user_address = Address::from_str("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B").unwrap();
    let amount = U256::from(100) * U256::exp10(18); // 100 ETH
    
    println!("修改用户余额...");
    fork_db.set_balance(user_address, amount).unwrap();
    
    // 验证修改
    let new_balance = fork_db.get_balance(user_address).await.unwrap();
    println!("用户新余额: {} ETH", new_balance);
    
    // 4. 批量操作示例
    let addresses = vec![
        Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045").unwrap(), // vitalik.eth
        Address::from_str("0x1db3439a222c519ab44bb1144fc28167b4fa6ee6").unwrap() // 随机地址
    ];
    
    println!("批量查询余额...");
    for addr in addresses {
        let balance = fork_db.get_balance(addr).await.unwrap();
        println!("地址 {} 的余额: {}", addr, balance);
    }
}

这个完整示例展示了:

  1. 使用缓存目录创建分叉数据库
  2. 查询特定合约的余额
  3. 修改用户地址的余额
  4. 批量查询多个地址的余额

要运行此示例,请确保:

  1. 替换YOUR_INFURA_KEY为实际的Infura API密钥
  2. 确保已安装tokio运行时
  3. Cargo.toml中包含所有必要的依赖项
回到顶部