newton-core 0.4.16

newton protocol core sdk
//! Chain utilities and constants.
//!
//! This module contains:
//! - Pure chain constants and classification functions (always available)
//! - RPC-dependent functions for querying on-chain state (requires `rpc` feature)
//!
//! The constants and classification functions are re-exported in `io::chain`
//! for backward compatibility.

#[cfg(feature = "rpc")]
use crate::common::address::get_challenge_verifier_address;

#[cfg(feature = "rpc")]
use crate::{
    challenge_verifier::ChallengeVerifier, newton_policy_client::NewtonPolicyClient,
    newton_prover_task_manager::NewtonProverTaskManager,
};
#[cfg(feature = "rpc")]
use alloy::{
    primitives::{Address, ChainId, B256},
    providers::Provider,
};
#[cfg(feature = "rpc")]
use newton_common::get_provider;
#[cfg(feature = "rpc")]
use tracing::info;

// Re-export commonly used address functions for convenience
#[cfg(feature = "rpc")]
pub use crate::common::address::{
    get_mock_newton_policy_client_address, get_newton_prover_service_manager, get_newton_prover_task_manager,
};

/// Get the chain ID from an RPC URL
#[cfg(feature = "rpc")]
pub async fn get_chain_id(rpc_url: &str) -> ChainId {
    let provider = get_provider(rpc_url);
    provider.get_chain_id().await.expect("Failed to get chain ID")
}

/// Fetches the task response window block from the task manager contract
/// This is the number of blocks (taskResponseWindowBlock) that defines the time window
/// within which operators must submit their task responses
#[cfg(feature = "rpc")]
pub async fn get_task_response_window_blocks(rpc_url: &str) -> eyre::Result<u32> {
    let chain_id = get_chain_id(rpc_url).await;
    let provider = get_provider(rpc_url);
    match chain_id {
        31337 | 31338 | 11155111 | 1 | 84532 => {
            let task_manager_addr = get_newton_prover_task_manager(chain_id).await?;
            let task_manager = NewtonProverTaskManager::new(task_manager_addr, &provider);

            let task_response_window_block = task_manager
                .taskResponseWindowBlock()
                .call()
                .await
                .map_err(|e| eyre::eyre!("Failed to fetch task response window block from task manager: {}", e))?;

            Ok(task_response_window_block)
        }
        _ => Err(eyre::eyre!("Unsupported chain id: {}", chain_id)),
    }
}

/// Fetches the challenge window from the challenge verifier contract
#[cfg(feature = "rpc")]
pub async fn get_task_challenge_window_blocks(rpc_url: &str) -> eyre::Result<u32> {
    let chain_id = get_chain_id(rpc_url).await;
    let provider = get_provider(rpc_url);
    match chain_id {
        31337 | 31338 | 11155111 | 1 | 84532 => {
            let challenge_verifier_addr = get_challenge_verifier_address(chain_id).await?;
            let challenge_verifier = ChallengeVerifier::new(challenge_verifier_addr, &provider);

            let challenge_window_blocks = challenge_verifier
                .taskChallengeWindowBlock()
                .call()
                .await
                .map_err(|e| eyre::eyre!("Failed to fetch task challenge window from challenge verifier: {}", e))?;

            Ok(challenge_window_blocks)
        }
        _ => Err(eyre::eyre!("Unsupported chain id: {}", chain_id)),
    }
}

/// Get block duration time in milliseconds per chain
/// Returns the block time in milliseconds.
///
/// # Supported Chains
///
/// **Ethereum L1:**
/// - 1: Ethereum Mainnet (~12s)
/// - 11155111: Sepolia Testnet (~12s)
///
/// **Optimism Ecosystem (OP Stack):**
/// - 10: Optimism (~2s)
/// - 11155420: Optimism Sepolia (~2s)
/// - 8453: Base (~2s)
/// - 84532: Base Sepolia (~2s)
/// - 7777777: Zora (~2s)
/// - 34443: Mode (~2s)
///
/// **Arbitrum Ecosystem:**
/// - 42161: Arbitrum One (~250ms)
/// - 421614: Arbitrum Sepolia (~250ms)
/// - 42170: Arbitrum Nova (~250ms)
///
/// **Polygon:**
/// - 137: Polygon PoS (~2s)
/// - 80002: Polygon Amoy Testnet (~2s)
///
/// **zkSync:**
/// - 324: zkSync Era (~1s)
/// - 300: zkSync Sepolia (~1s)
///
/// **Scroll:**
/// - 534352: Scroll (~3s)
/// - 534351: Scroll Sepolia (~3s)
///
/// **Linea:**
/// - 59144: Linea (~2s)
/// - 59141: Linea Sepolia (~2s)
///
/// **Blast:**
/// - 81457: Blast (~2s)
/// - 168587773: Blast Sepolia (~2s)
///
/// **Mantle:**
/// - 5000: Mantle (~2s)
/// - 5003: Mantle Sepolia (~2s)
pub fn get_block_duration_ms(chain_id: ChainId) -> Result<u64, String> {
    match chain_id {
        // Ethereum L1 - 12 second blocks
        1 | 11155111 => Ok(12000),
        // Local anvil (31337 source, 31338 destination for multichain testing)
        1337 | 31337 | 31338 => Ok(1000), // mock value

        // Optimism Ecosystem (OP Stack) - 2 second blocks
        10 |        // Optimism
        11155420 |  // Optimism Sepolia
        8453 |      // Base
        84532 |     // Base Sepolia
        7777777 |   // Zora
        34443       // Mode
        => Ok(2000),

        // Arbitrum - 250ms blocks
        42161 |     // Arbitrum One
        421614 |    // Arbitrum Sepolia
        42170       // Arbitrum Nova
        => Ok(250),

        // Polygon PoS - 2 second blocks
        137 |       // Polygon PoS
        80002       // Polygon Amoy
        => Ok(2000),

        // zkSync Era - 1 second blocks
        324 |       // zkSync Era
        300         // zkSync Sepolia
        => Ok(1000),

        // Scroll - 3 second blocks
        534352 |    // Scroll
        534351      // Scroll Sepolia
        => Ok(3000),

        // Linea - 2 second blocks
        59144 |     // Linea
        59141       // Linea Sepolia
        => Ok(2000),

        // Blast - 2 second blocks
        81457 |     // Blast
        168587773   // Blast Sepolia
        => Ok(2000),

        // Mantle - 2 second blocks
        5000 |      // Mantle
        5003        // Mantle Sepolia
        => Ok(2000),

        _ => Err(format!("Unsupported chain ID: {}. Please add support for this chain.", chain_id)),
    }
}

/// Returns the block gas limit for the chain. Static table avoids an
/// `eth_getBlock` roundtrip at startup since limits change rarely.
pub fn get_block_gas_limit(chain_id: ChainId) -> u64 {
    match chain_id {
        1 | 11155111 => 36_000_000,                  // Ethereum L1 / Sepolia (post-Pectra)
        1337 | 31337 | 31338 => 30_000_000,          // anvil
        10 | 11155420 | 8453 | 84532 => 240_000_000, // OP Stack (Optimism, Base)
        42161 | 421614 | 42170 => 32_000_000,        // Arbitrum
        137 | 80002 | 324 | 300 | 534352 | 534351 | 59144 | 59141 | 81457 | 168587773 | 7777777 | 34443 | 5000
        | 5003 => 30_000_000,
        _ => 30_000_000, // conservative default
    }
}

/// Fetches block time in milliseconds for the chain
#[cfg(feature = "rpc")]
pub async fn get_block_time_ms(rpc_url: &str) -> eyre::Result<u64> {
    let chain_id = get_chain_id(rpc_url).await;
    info!("Fetching block time for chain ID {}", chain_id);
    let block_time_ms =
        get_block_duration_ms(chain_id).map_err(|e| eyre::eyre!("Failed to get block duration: {}", e))?;

    info!("Block time for chain ID {} is {} ms", chain_id, block_time_ms);
    if block_time_ms == 0 {
        Err(eyre::eyre!("Block time is zero"))
    } else {
        Ok(block_time_ms)
    }
}

/// Fetches epoch time in number of blocks from the task manager contract
/// This is the number of blocks (epochBlocks) that defines the epoch duration
#[cfg(feature = "rpc")]
pub async fn get_epoch_blocks(rpc_url: &str) -> eyre::Result<u64> {
    let chain_id = get_chain_id(rpc_url).await;
    let provider = get_provider(rpc_url);
    match chain_id {
        31337 | 31338 | 11155111 | 1 | 84532 => {
            let task_manager_addr = get_newton_prover_task_manager(chain_id).await?;
            let task_manager = NewtonProverTaskManager::new(task_manager_addr, &provider);

            let epoch_blocks: u32 = task_manager
                .epochBlocks()
                .call()
                .await
                .map_err(|e| eyre::eyre!("Failed to fetch epoch blocks from task manager: {}", e))?;

            Ok(epoch_blocks as u64)
        }
        _ => Err(eyre::eyre!("Unsupported chain id: {}", chain_id)),
    }
}

/// Ethereum Mainnet
pub const ETHEREUM_MAINNET: u64 = 1;
/// Sepolia Testnet
pub const SEPOLIA: u64 = 11155111;
/// Local Anvil (primary)
pub const LOCAL_ANVIL: u64 = 31337;
/// Local Anvil (secondary, destination in dual-anvil mode)
pub const LOCAL_ANVIL_DESTINATION: u64 = 31338;

/// Base Mainnet (currently EigenLayer-supported; migrating to Newton registry)
pub const BASE_MAINNET: u64 = 8453;
/// Base Sepolia (currently EigenLayer-supported; migrating to Newton registry)
pub const BASE_SEPOLIA: u64 = 84532;

/// Optimism Mainnet
pub const OPTIMISM_MAINNET: u64 = 10;
/// Optimism Sepolia
pub const OPTIMISM_SEPOLIA: u64 = 11155420;

/// Arbitrum One
pub const ARBITRUM_ONE: u64 = 42161;
/// Arbitrum Sepolia
pub const ARBITRUM_SEPOLIA: u64 = 421614;

/// Polygon Mainnet
pub const POLYGON_MAINNET: u64 = 137;
/// Polygon Amoy (testnet)
pub const POLYGON_AMOY: u64 = 80002;

/// Returns true if the chain is a source chain (where EigenLayer is deployed).
#[inline]
pub const fn is_source_chain(chain_id: u64) -> bool {
    matches!(chain_id, ETHEREUM_MAINNET | SEPOLIA | LOCAL_ANVIL)
}

/// Returns true if the chain is a destination chain.
/// A destination chain is any supported chain that is NOT a source chain.
#[inline]
pub const fn is_destination_chain(chain_id: u64) -> bool {
    is_supported_chain(chain_id) && !is_source_chain(chain_id)
}

/// Returns true if EigenLayer's Generator service currently operates on this destination.
///
/// Base chains temporarily use EigenLayer's CrossChainRegistry. Newton plans to
/// migrate all destinations (including Base) to NewtonCrossChainRegistry for
/// full self-sufficiency. Once migrated, this function will return false for all
/// chains and `requires_newton_registry` will return true for all destinations.
#[inline]
pub const fn is_eigenlayer_supported_destination(chain_id: u64) -> bool {
    matches!(chain_id, BASE_MAINNET | BASE_SEPOLIA)
}

/// Returns true if Newton's CrossChainRegistry is needed for this chain.
/// Currently required for destinations where EigenLayer doesn't operate.
/// Base temporarily uses EigenLayer's CrossChainRegistry but will migrate to
/// Newton's registry for self-sufficiency.
#[inline]
pub const fn requires_newton_registry(chain_id: u64) -> bool {
    is_destination_chain(chain_id) && !is_eigenlayer_supported_destination(chain_id)
}

/// Returns the source chain ID for any given chain.
#[inline]
pub const fn get_source_chain_id(chain_id: u64) -> Option<u64> {
    if is_source_chain(chain_id) {
        return Some(chain_id);
    }

    // Local destination maps to local source
    if chain_id == LOCAL_ANVIL_DESTINATION {
        return Some(LOCAL_ANVIL);
    }

    // Testnet destinations map to Sepolia
    if is_testnet(chain_id) {
        return Some(SEPOLIA);
    }

    // Mainnet destinations map to Ethereum
    if is_mainnet(chain_id) {
        return Some(ETHEREUM_MAINNET);
    }

    None // Unsupported chain
}

/// Returns true if this is a mainnet chain.
#[inline]
pub const fn is_mainnet(chain_id: u64) -> bool {
    matches!(
        chain_id,
        ETHEREUM_MAINNET | BASE_MAINNET | OPTIMISM_MAINNET | ARBITRUM_ONE | POLYGON_MAINNET
    )
}

/// Returns true if this is a testnet chain.
#[inline]
pub const fn is_testnet(chain_id: u64) -> bool {
    matches!(
        chain_id,
        SEPOLIA | BASE_SEPOLIA | OPTIMISM_SEPOLIA | ARBITRUM_SEPOLIA | POLYGON_AMOY
    )
}

/// Returns true if this is a local development chain.
#[inline]
pub const fn is_local(chain_id: u64) -> bool {
    matches!(chain_id, LOCAL_ANVIL | LOCAL_ANVIL_DESTINATION)
}

/// Returns true if the chain is supported by Newton Prover AVS.
#[inline]
pub const fn is_supported_chain(chain_id: u64) -> bool {
    is_mainnet(chain_id) || is_testnet(chain_id) || is_local(chain_id)
}