forest-filecoin 0.36.1

Rust Filecoin implementation.
Documentation
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

//! Shared test fixtures for the trace module.

use crate::db::MemoryDB;
use crate::networks::ACTOR_BUNDLES_METADATA;
use crate::rpc::eth::trace::state_diff::build_state_diff;
use crate::rpc::eth::trace::types::StateDiff;
use crate::rpc::eth::types::EthAddress;
use crate::shim::address::Address as FilecoinAddress;
use crate::shim::econ::TokenAmount;
use crate::shim::machine::BuiltinActor;
use crate::shim::state_tree::{ActorState, StateTree, StateTreeVersion};
use crate::utils::db::CborStoreExt as _;
use ahash::HashSet;
use cid::Cid;
use std::sync::Arc;

pub fn create_test_actor(balance_atto: u64, sequence: u64) -> ActorState {
    ActorState::new(
        Cid::default(), // Non-EVM actor code CID
        Cid::default(), // State CID (not used for non-EVM)
        TokenAmount::from_atto(balance_atto),
        sequence,
        None, // No delegated address
    )
}

pub fn get_evm_actor_code_cid() -> Option<Cid> {
    for bundle in ACTOR_BUNDLES_METADATA.values() {
        if bundle.actor_major_version().ok() == Some(17)
            && let Ok(cid) = bundle.manifest.get(BuiltinActor::EVM)
        {
            return Some(cid);
        }
    }
    None
}

pub fn create_evm_actor_with_bytecode(
    store: &MemoryDB,
    balance_atto: u64,
    actor_sequence: u64,
    evm_nonce: u64,
    bytecode: Option<&[u8]>,
) -> Option<ActorState> {
    use fvm_ipld_blockstore::Blockstore as _;

    let evm_code_cid = get_evm_actor_code_cid()?;

    // Store bytecode as raw bytes (not CBOR-encoded)
    let bytecode_cid = if let Some(code) = bytecode {
        use multihash_codetable::MultihashDigest;
        let mh = multihash_codetable::Code::Blake2b256.digest(code);
        let cid = Cid::new_v1(fvm_ipld_encoding::IPLD_RAW, mh);
        store.put_keyed(&cid, code).ok()?;
        cid
    } else {
        Cid::default()
    };

    let bytecode_hash = if let Some(code) = bytecode {
        use keccak_hash::keccak;
        let hash = keccak(code);
        fil_actor_evm_state::v17::BytecodeHash::from(hash.0)
    } else {
        fil_actor_evm_state::v17::BytecodeHash::EMPTY
    };

    let evm_state = fil_actor_evm_state::v17::State {
        bytecode: bytecode_cid,
        bytecode_hash,
        contract_state: Cid::default(),
        transient_data: None,
        nonce: evm_nonce,
        tombstone: None,
    };

    let state_cid = store.put_cbor_default(&evm_state).ok()?;

    Some(ActorState::new(
        evm_code_cid,
        state_cid,
        TokenAmount::from_atto(balance_atto),
        actor_sequence,
        None,
    ))
}

/// An alive EVM actor whose storage KAMT root is `contract_state`.
pub fn create_evm_actor_with_storage(store: &MemoryDB, contract_state: Cid) -> Option<ActorState> {
    let evm_state = fil_actor_evm_state::v17::State {
        bytecode: Cid::default(),
        bytecode_hash: fil_actor_evm_state::v17::BytecodeHash::EMPTY,
        contract_state,
        transient_data: None,
        nonce: 0,
        tombstone: None,
    };
    Some(ActorState::new(
        get_evm_actor_code_cid()?,
        store.put_cbor_default(&evm_state).ok()?,
        TokenAmount::from_atto(0),
        0,
        None,
    ))
}

/// Like [`create_evm_actor_with_bytecode`] but marks the actor self-destructed via a tombstone,
/// so it must report no bytecode, a zero nonce and empty storage even though `bytecode`,
/// `evm_nonce` and `contract_state` are all populated.
pub fn create_tombstoned_evm_actor(
    store: &MemoryDB,
    bytecode: &[u8],
    evm_nonce: u64,
    contract_state: Cid,
) -> Option<ActorState> {
    use fvm_ipld_blockstore::Blockstore as _;
    use multihash_codetable::MultihashDigest as _;

    let evm_code_cid = get_evm_actor_code_cid()?;
    let mh = multihash_codetable::Code::Blake2b256.digest(bytecode);
    let bytecode_cid = Cid::new_v1(fvm_ipld_encoding::IPLD_RAW, mh);
    store.put_keyed(&bytecode_cid, bytecode).ok()?;

    let evm_state = fil_actor_evm_state::v17::State {
        bytecode: bytecode_cid,
        bytecode_hash: fil_actor_evm_state::v17::BytecodeHash::from(
            keccak_hash::keccak(bytecode).0,
        ),
        contract_state,
        transient_data: None,
        nonce: evm_nonce,
        tombstone: Some(fil_actor_evm_state::v17::Tombstone {
            origin: 0,
            nonce: 0,
        }),
    };
    let state_cid = store.put_cbor_default(&evm_state).ok()?;
    Some(ActorState::new(
        evm_code_cid,
        state_cid,
        TokenAmount::from_atto(0),
        0,
        None,
    ))
}

pub fn create_masked_id_eth_address(actor_id: u64) -> EthAddress {
    EthAddress::from_actor_id(actor_id)
}

pub struct TestStateTrees {
    pub store: Arc<MemoryDB>,
    pub pre_state: StateTree<Arc<MemoryDB>>,
    pub post_state: StateTree<Arc<MemoryDB>>,
}

impl TestStateTrees {
    pub fn new() -> anyhow::Result<Self> {
        let store = Arc::new(MemoryDB::default());
        let pre_state = StateTree::new(&store, StateTreeVersion::V5)?;
        let post_state = StateTree::new(&store, StateTreeVersion::V5)?;
        Ok(Self {
            store,
            pre_state,
            post_state,
        })
    }

    /// Create state trees with different actors in pre and post.
    pub fn with_changed_actor(
        actor_id: u64,
        pre_actor: ActorState,
        post_actor: ActorState,
    ) -> anyhow::Result<Self> {
        let store = Arc::new(MemoryDB::default());
        let mut pre_state = StateTree::new(&store, StateTreeVersion::V5)?;
        let mut post_state = StateTree::new(&store, StateTreeVersion::V5)?;
        let addr = FilecoinAddress::new_id(actor_id);
        pre_state.set_actor(&addr, pre_actor)?;
        post_state.set_actor(&addr, post_actor)?;
        Ok(Self {
            store,
            pre_state,
            post_state,
        })
    }

    /// Create state trees with actor only in post (creation scenario).
    pub fn with_created_actor(actor_id: u64, post_actor: ActorState) -> anyhow::Result<Self> {
        let store = Arc::new(MemoryDB::default());
        let pre_state = StateTree::new(&store, StateTreeVersion::V5)?;
        let mut post_state = StateTree::new(&store, StateTreeVersion::V5)?;
        let addr = FilecoinAddress::new_id(actor_id);
        post_state.set_actor(&addr, post_actor)?;
        Ok(Self {
            store,
            pre_state,
            post_state,
        })
    }

    /// Create state trees with actor only in pre (deletion scenario).
    pub fn with_deleted_actor(actor_id: u64, pre_actor: ActorState) -> anyhow::Result<Self> {
        let store = Arc::new(MemoryDB::default());
        let mut pre_state = StateTree::new(&store, StateTreeVersion::V5)?;
        let post_state = StateTree::new(&store, StateTreeVersion::V5)?;
        let addr = FilecoinAddress::new_id(actor_id);
        pre_state.set_actor(&addr, pre_actor)?;
        Ok(Self {
            store,
            pre_state,
            post_state,
        })
    }

    /// Build state diff for given touched addresses.
    pub fn build_diff(&self, touched_addresses: &HashSet<EthAddress>) -> anyhow::Result<StateDiff> {
        build_state_diff(
            self.store.as_ref(),
            &self.pre_state,
            &self.post_state,
            touched_addresses,
        )
    }
}