From b011658ca901e0de04758aa6aa32bf28e1f5c164 Mon Sep 17 00:00:00 2001 From: Ayomide Adeniran <141429949+ayomideadeniran@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:11:51 +0100 Subject: [PATCH] Revert "feat(core): Add network config module for protocol upgrade impact analysis" --- core/src/lib.rs | 1 - core/src/main.rs | 45 ----- core/src/network_config.rs | 397 ------------------------------------- core/src/simulation.rs | 44 +--- 4 files changed, 8 insertions(+), 479 deletions(-) delete mode 100644 core/src/network_config.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 9edf4eb..a36a823 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,3 +1,2 @@ -pub mod network_config; pub mod parser; pub mod simulation; diff --git a/core/src/main.rs b/core/src/main.rs index e337d01..ae707a0 100644 --- a/core/src/main.rs +++ b/core/src/main.rs @@ -1,12 +1,10 @@ mod auth; mod benchmarks; mod errors; -pub mod network_config; mod parser; mod simulation; use crate::errors::AppError; -use crate::network_config::NetworkConfig; use crate::simulation::{SimulationCache, SimulationEngine, SimulationResult}; use axum::{ extract::{Json, State}, @@ -74,23 +72,6 @@ pub struct AnalyzeRequest { pub args: Option>, /// Map of Key-Base64 to Value-Base64 ledger entry overrides pub ledger_overrides: Option>, - /// Shadow network configuration for protocol upgrade impact analysis. - /// - /// Accepts either: - /// - A preset name: `"protocol_21"`, `"p21"`, `"current"`, - /// `"protocol_22"`, `"p22"`, `"next"`, `"custom"`, `"private"` - /// - A full `NetworkConfig` JSON object with custom parameters - pub network_config: Option, -} - -/// Flexible input: either a preset name or a full custom config object. -#[derive(Debug, Deserialize, ToSchema)] -#[serde(untagged)] -pub enum NetworkConfigInput { - /// A preset name like `"protocol_22"` or `"next"`. - Preset(String), - /// A full custom configuration object. - Custom(NetworkConfig), } #[derive(Serialize, ToSchema)] @@ -112,10 +93,6 @@ pub struct ResourceReport { pub transaction_size_bytes: u64, /// Report showing which data was injected vs live pub state_dependency: Option>, - /// Protocol upgrade impact comparison (present when `network_config` was - /// supplied in the request). - #[serde(skip_serializing_if = "Option::is_none")] - pub protocol_impact: Option, } #[derive(Serialize, ToSchema, Debug)] @@ -140,10 +117,6 @@ fn to_report(result: &SimulationResult) -> ResourceReport { }) .collect() }), - protocol_impact: result - .protocol_impact - .as_ref() - .and_then(|impact| serde_json::to_value(impact).ok()), } } @@ -172,23 +145,6 @@ async fn analyze( ); let args = payload.args.clone().unwrap_or_default(); - - // Resolve the optional shadow network config. - let shadow_config: Option = match &payload.network_config { - Some(NetworkConfigInput::Preset(name)) => { - let cfg = network_config::resolve_preset(name).ok_or_else(|| { - AppError::BadRequest(format!( - "Unknown network config preset '{}'. \ - Valid presets: protocol_21, p21, current, protocol_22, p22, next, upcoming, custom, private", - name - )) - })?; - Some(cfg) - } - Some(NetworkConfigInput::Custom(cfg)) => Some(cfg.clone()), - None => None, - }; - let cache_key = SimulationCache::generate_key(&payload.contract_id, &payload.function_name, &args); @@ -203,7 +159,6 @@ async fn analyze( &payload.function_name, args, payload.ledger_overrides.clone(), - shadow_config, ) .await .map_err(|e| AppError::Internal(format!("Simulation failed: {}", e)))?; diff --git a/core/src/network_config.rs b/core/src/network_config.rs deleted file mode 100644 index aad6ff6..0000000 --- a/core/src/network_config.rs +++ /dev/null @@ -1,397 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::simulation::SorobanResources; - -// ── Protocol cost parameters ────────────────────────────────────────────────── - -/// Network-level cost parameters that govern how resource consumption maps to -/// fees (stroops). Different protocol versions use different rates. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct NetworkConfig { - /// Human-readable label for this configuration. - pub name: String, - /// Protocol version number (e.g. 21, 22). - pub protocol_version: u32, - - // ── Fee rates ───────────────────────────────────────────────────────── - /// CPU instructions per fee unit (higher = cheaper per instruction). - pub cpu_insns_per_fee_unit: u64, - /// Memory bytes per fee unit. - pub mem_bytes_per_fee_unit: u64, - /// Ledger I/O bytes per fee unit. - pub ledger_bytes_per_fee_unit: u64, - /// Transaction size bytes per fee unit. - pub tx_size_bytes_per_fee_unit: u64, - - // ── Resource limits (per transaction) ───────────────────────────────── - /// Maximum CPU instructions a single transaction may consume. - pub tx_max_instructions: u64, - /// Maximum memory bytes a single transaction may consume. - pub tx_max_memory_bytes: u64, - /// Maximum ledger read bytes per transaction. - pub tx_max_read_bytes: u64, - /// Maximum ledger write bytes per transaction. - pub tx_max_write_bytes: u64, - /// Maximum transaction envelope size in bytes. - pub tx_max_size_bytes: u64, -} - -impl NetworkConfig { - /// Calculate the total fee (stroops) for a given resource footprint under - /// this configuration's cost rates. - pub fn calculate_cost(&self, resources: &SorobanResources) -> u64 { - let cpu_fee = resources.cpu_instructions / self.cpu_insns_per_fee_unit; - let mem_fee = resources.ram_bytes / self.mem_bytes_per_fee_unit; - let ledger_fee = (resources.ledger_read_bytes + resources.ledger_write_bytes) - / self.ledger_bytes_per_fee_unit; - let size_fee = resources.transaction_size_bytes / self.tx_size_bytes_per_fee_unit; - cpu_fee + mem_fee + ledger_fee + size_fee - } - - /// Check which resource limits would be exceeded under this configuration. - pub fn check_limits(&self, resources: &SorobanResources) -> Vec { - let mut exceeded = Vec::new(); - if resources.cpu_instructions > self.tx_max_instructions { - exceeded.push(LimitExceeded { - resource: "cpu_instructions".to_string(), - used: resources.cpu_instructions, - limit: self.tx_max_instructions, - }); - } - if resources.ram_bytes > self.tx_max_memory_bytes { - exceeded.push(LimitExceeded { - resource: "ram_bytes".to_string(), - used: resources.ram_bytes, - limit: self.tx_max_memory_bytes, - }); - } - if resources.ledger_read_bytes > self.tx_max_read_bytes { - exceeded.push(LimitExceeded { - resource: "ledger_read_bytes".to_string(), - used: resources.ledger_read_bytes, - limit: self.tx_max_read_bytes, - }); - } - if resources.ledger_write_bytes > self.tx_max_write_bytes { - exceeded.push(LimitExceeded { - resource: "ledger_write_bytes".to_string(), - used: resources.ledger_write_bytes, - limit: self.tx_max_write_bytes, - }); - } - if resources.transaction_size_bytes > self.tx_max_size_bytes { - exceeded.push(LimitExceeded { - resource: "transaction_size_bytes".to_string(), - used: resources.transaction_size_bytes, - limit: self.tx_max_size_bytes, - }); - } - exceeded - } -} - -/// A single resource limit that was exceeded. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct LimitExceeded { - pub resource: String, - pub used: u64, - pub limit: u64, -} - -// ── Pre-set protocol configurations ─────────────────────────────────────────── - -/// Protocol 21 — Current Testnet (baseline). -/// -/// Cost rates match the hardcoded values previously in -/// `SimulationEngine::calculate_cost`. -pub fn protocol_21() -> NetworkConfig { - NetworkConfig { - name: "Protocol 21 (Current Testnet)".to_string(), - protocol_version: 21, - cpu_insns_per_fee_unit: 10_000, - mem_bytes_per_fee_unit: 1_024, - ledger_bytes_per_fee_unit: 1_024, - tx_size_bytes_per_fee_unit: 1_024, - tx_max_instructions: 100_000_000, - tx_max_memory_bytes: 40 * 1024 * 1024, // 40 MiB - tx_max_read_bytes: 200 * 1024, // 200 KiB - tx_max_write_bytes: 65_536, // 64 KiB - tx_max_size_bytes: 71_680, // 70 KiB - } -} - -/// Protocol 22 — Upcoming / Next. -/// -/// Models an anticipated upgrade with higher CPU/memory budgets but tighter -/// per-unit costs to incentivise efficient contracts. -pub fn protocol_22() -> NetworkConfig { - NetworkConfig { - name: "Protocol 22 (Upcoming/Next)".to_string(), - protocol_version: 22, - // Slightly cheaper CPU (larger divisor → lower per-unit fee). - cpu_insns_per_fee_unit: 12_500, - // Memory cost stays the same. - mem_bytes_per_fee_unit: 1_024, - // Ledger I/O gets more expensive (smaller divisor → higher fee). - ledger_bytes_per_fee_unit: 768, - tx_size_bytes_per_fee_unit: 1_024, - // Higher CPU budget — allows more complex contracts. - tx_max_instructions: 200_000_000, - tx_max_memory_bytes: 64 * 1024 * 1024, // 64 MiB - tx_max_read_bytes: 200 * 1024, - tx_max_write_bytes: 131_072, // 128 KiB - tx_max_size_bytes: 71_680, - } -} - -/// Custom / Private Network — sensible defaults that can be overridden via -/// the API request body. -pub fn custom_private() -> NetworkConfig { - NetworkConfig { - name: "Custom Private Network".to_string(), - protocol_version: 21, - cpu_insns_per_fee_unit: 10_000, - mem_bytes_per_fee_unit: 1_024, - ledger_bytes_per_fee_unit: 1_024, - tx_size_bytes_per_fee_unit: 1_024, - tx_max_instructions: 500_000_000, // generous - tx_max_memory_bytes: 128 * 1024 * 1024, // 128 MiB - tx_max_read_bytes: 1024 * 1024, // 1 MiB - tx_max_write_bytes: 512 * 1024, // 512 KiB - tx_max_size_bytes: 256 * 1024, // 256 KiB - } -} - -/// Resolve a preset name to the corresponding `NetworkConfig`. -/// -/// Recognised names (case-insensitive): -/// - `"protocol_21"` / `"p21"` / `"current"` -/// - `"protocol_22"` / `"p22"` / `"next"` / `"upcoming"` -/// - `"custom"` / `"private"` -pub fn resolve_preset(name: &str) -> Option { - match name.to_lowercase().as_str() { - "protocol_21" | "p21" | "current" => Some(protocol_21()), - "protocol_22" | "p22" | "next" | "upcoming" => Some(protocol_22()), - "custom" | "private" => Some(custom_private()), - _ => None, - } -} - -// ── Impact comparison ───────────────────────────────────────────────────────── - -/// Side-by-side comparison of a transaction's cost under two protocol configs. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ProtocolImpact { - pub baseline: ProtocolCostSnapshot, - pub shadow: ProtocolCostSnapshot, - /// Signed difference: `shadow.cost_stroops - baseline.cost_stroops`. - /// Positive means the shadow config is *more* expensive. - pub cost_difference_stroops: i64, - /// Percentage change: `(shadow - baseline) / baseline * 100`. - pub cost_change_pct: f64, -} - -/// Cost snapshot under a single protocol configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ProtocolCostSnapshot { - pub config_name: String, - pub protocol_version: u32, - pub cost_stroops: u64, - pub limits_exceeded: Vec, -} - -/// Compare a resource footprint across two configurations and produce an -/// impact report. -pub fn compare( - resources: &SorobanResources, - baseline: &NetworkConfig, - shadow: &NetworkConfig, -) -> ProtocolImpact { - let baseline_cost = baseline.calculate_cost(resources); - let shadow_cost = shadow.calculate_cost(resources); - - let diff = shadow_cost as i64 - baseline_cost as i64; - let pct = if baseline_cost > 0 { - (diff as f64 / baseline_cost as f64) * 100.0 - } else { - 0.0 - }; - - ProtocolImpact { - baseline: ProtocolCostSnapshot { - config_name: baseline.name.clone(), - protocol_version: baseline.protocol_version, - cost_stroops: baseline_cost, - limits_exceeded: baseline.check_limits(resources), - }, - shadow: ProtocolCostSnapshot { - config_name: shadow.name.clone(), - protocol_version: shadow.protocol_version, - cost_stroops: shadow_cost, - limits_exceeded: shadow.check_limits(resources), - }, - cost_difference_stroops: diff, - cost_change_pct: pct, - } -} - -// ── Tests ───────────────────────────────────────────────────────────────────── - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_resources() -> SorobanResources { - SorobanResources { - cpu_instructions: 1_000_000, - ram_bytes: 2_048, - ledger_read_bytes: 512, - ledger_write_bytes: 256, - transaction_size_bytes: 1_024, - } - } - - #[test] - fn test_protocol_21_cost_matches_legacy() { - let r = sample_resources(); - let cfg = protocol_21(); - // Legacy formula: cpu/10000 + ram/1024 + (read+write)/1024 - let legacy = r.cpu_instructions / 10_000 - + r.ram_bytes / 1_024 - + (r.ledger_read_bytes + r.ledger_write_bytes) / 1_024 - + r.transaction_size_bytes / 1_024; - assert_eq!(cfg.calculate_cost(&r), legacy); - } - - #[test] - fn test_protocol_22_cheaper_cpu() { - let r = sample_resources(); - let p21 = protocol_21(); - let p22 = protocol_22(); - // P22 has a higher cpu_insns_per_fee_unit, so CPU portion is cheaper. - let p21_cpu = r.cpu_instructions / p21.cpu_insns_per_fee_unit; - let p22_cpu = r.cpu_instructions / p22.cpu_insns_per_fee_unit; - assert!(p22_cpu < p21_cpu, "P22 should have cheaper CPU fees"); - } - - #[test] - fn test_protocol_22_more_expensive_ledger() { - let r = sample_resources(); - let p21 = protocol_21(); - let p22 = protocol_22(); - let p21_ledger = - (r.ledger_read_bytes + r.ledger_write_bytes) / p21.ledger_bytes_per_fee_unit; - let p22_ledger = - (r.ledger_read_bytes + r.ledger_write_bytes) / p22.ledger_bytes_per_fee_unit; - assert!( - p22_ledger >= p21_ledger, - "P22 should have same or higher ledger fees" - ); - } - - #[test] - fn test_compare_produces_correct_diff() { - let r = sample_resources(); - let impact = compare(&r, &protocol_21(), &protocol_22()); - let expected_diff = impact.shadow.cost_stroops as i64 - impact.baseline.cost_stroops as i64; - assert_eq!(impact.cost_difference_stroops, expected_diff); - } - - #[test] - fn test_compare_percentage() { - let r = sample_resources(); - let impact = compare(&r, &protocol_21(), &protocol_22()); - let expected_pct = - (impact.cost_difference_stroops as f64 / impact.baseline.cost_stroops as f64) * 100.0; - assert!((impact.cost_change_pct - expected_pct).abs() < 0.001); - } - - #[test] - fn test_check_limits_within_budget() { - let r = sample_resources(); - assert!(protocol_21().check_limits(&r).is_empty()); - assert!(protocol_22().check_limits(&r).is_empty()); - assert!(custom_private().check_limits(&r).is_empty()); - } - - #[test] - fn test_check_limits_exceeded() { - let r = SorobanResources { - cpu_instructions: 500_000_000, // exceeds P21 limit of 100M - ram_bytes: 2_048, - ledger_read_bytes: 512, - ledger_write_bytes: 512, - transaction_size_bytes: 1_024, - }; - let exceeded = protocol_21().check_limits(&r); - assert_eq!(exceeded.len(), 1); - assert_eq!(exceeded[0].resource, "cpu_instructions"); - assert_eq!(exceeded[0].used, 500_000_000); - assert_eq!(exceeded[0].limit, 100_000_000); - } - - #[test] - fn test_resolve_preset_case_insensitive() { - assert!(resolve_preset("protocol_21").is_some()); - assert!(resolve_preset("P21").is_some()); - assert!(resolve_preset("CURRENT").is_some()); - assert!(resolve_preset("protocol_22").is_some()); - assert!(resolve_preset("Next").is_some()); - assert!(resolve_preset("custom").is_some()); - assert!(resolve_preset("unknown").is_none()); - } - - #[test] - fn test_resolve_preset_returns_correct_version() { - let p21 = resolve_preset("p21").unwrap(); - assert_eq!(p21.protocol_version, 21); - let p22 = resolve_preset("p22").unwrap(); - assert_eq!(p22.protocol_version, 22); - } - - #[test] - fn test_custom_private_generous_limits() { - let cfg = custom_private(); - assert!(cfg.tx_max_instructions > protocol_21().tx_max_instructions); - assert!(cfg.tx_max_memory_bytes > protocol_21().tx_max_memory_bytes); - } - - #[test] - fn test_network_config_serialization() { - let cfg = protocol_21(); - let json = serde_json::to_string(&cfg).unwrap(); - let deserialized: NetworkConfig = serde_json::from_str(&json).unwrap(); - assert_eq!(cfg, deserialized); - } - - #[test] - fn test_protocol_impact_serialization() { - let r = sample_resources(); - let impact = compare(&r, &protocol_21(), &protocol_22()); - let json = serde_json::to_string(&impact).unwrap(); - let deserialized: ProtocolImpact = serde_json::from_str(&json).unwrap(); - assert_eq!(impact.baseline, deserialized.baseline); - assert_eq!(impact.shadow, deserialized.shadow); - assert_eq!( - impact.cost_difference_stroops, - deserialized.cost_difference_stroops - ); - // f64 round-trip through JSON may introduce tiny precision differences. - assert!((impact.cost_change_pct - deserialized.cost_change_pct).abs() < 1e-10); - } - - #[test] - fn test_zero_resources_zero_cost() { - let r = SorobanResources::default(); - assert_eq!(protocol_21().calculate_cost(&r), 0); - assert_eq!(protocol_22().calculate_cost(&r), 0); - } - - #[test] - fn test_compare_identical_configs() { - let r = sample_resources(); - let impact = compare(&r, &protocol_21(), &protocol_21()); - assert_eq!(impact.cost_difference_stroops, 0); - assert!((impact.cost_change_pct - 0.0).abs() < 0.001); - } -} diff --git a/core/src/simulation.rs b/core/src/simulation.rs index d904027..502ab93 100644 --- a/core/src/simulation.rs +++ b/core/src/simulation.rs @@ -1,4 +1,3 @@ -use crate::network_config::{self, NetworkConfig, ProtocolImpact}; use crate::parser::ArgParser; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; use reqwest::Client; @@ -70,10 +69,6 @@ pub struct SimulationResult { pub cost_stroops: u64, #[serde(skip_serializing_if = "Option::is_none")] pub state_dependency: Option>, - /// Present when a shadow network config was requested — shows cost - /// comparison between the baseline (Protocol 21) and the shadow config. - #[serde(skip_serializing_if = "Option::is_none")] - pub protocol_impact: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -178,7 +173,6 @@ impl SimulationEngine { function_name: &str, args: Vec, ledger_overrides: Option>, - shadow_config: Option, ) -> Result { if contract_id.is_empty() { return Err(SimulationError::NodeError( @@ -188,25 +182,14 @@ impl SimulationEngine { if let Some(overrides) = ledger_overrides { if !overrides.is_empty() { - let mut result = self + return self .simulate_locally(contract_id, function_name, args, overrides) - .await?; - if let Some(shadow) = shadow_config { - result.protocol_impact = - Some(self.compute_protocol_impact(&result.resources, &shadow)); - } - return Ok(result); + .await; } } let transaction_xdr = self.create_invoke_transaction(contract_id, function_name, args)?; - let mut result = self.simulate_transaction(&transaction_xdr).await?; - - if let Some(shadow) = shadow_config { - result.protocol_impact = Some(self.compute_protocol_impact(&result.resources, &shadow)); - } - - Ok(result) + self.simulate_transaction(&transaction_xdr).await } async fn simulate_transaction( @@ -316,7 +299,6 @@ impl SimulationEngine { latest_ledger: rpc_result.latest_ledger, cost_stroops, state_dependency: None, - protocol_impact: None, }) } @@ -375,7 +357,6 @@ impl SimulationEngine { total_bytes } - #[allow(clippy::only_used_in_recursion)] fn estimate_scval_size(&self, scval: &soroban_sdk::xdr::ScVal) -> u64 { use soroban_sdk::xdr::ScVal; match scval { @@ -409,18 +390,10 @@ impl SimulationEngine { } fn calculate_cost(&self, resources: &SorobanResources) -> u64 { - network_config::protocol_21().calculate_cost(resources) - } - - /// Compare the resource footprint against the baseline (Protocol 21) and - /// the caller-supplied shadow configuration. - fn compute_protocol_impact( - &self, - resources: &SorobanResources, - shadow: &NetworkConfig, - ) -> ProtocolImpact { - let baseline = network_config::protocol_21(); - network_config::compare(resources, &baseline, shadow) + let cpu_cost = resources.cpu_instructions / 10000; + let ram_cost = resources.ram_bytes / 1024; + let ledger_cost = (resources.ledger_read_bytes + resources.ledger_write_bytes) / 1024; + cpu_cost + ram_cost + ledger_cost } /// Create invoke transaction for contract call @@ -753,7 +726,7 @@ mod tests { async fn test_simulate_from_contract_id_empty() { let engine = SimulationEngine::new("https://test.com".to_string()); let result = engine - .simulate_from_contract_id("", "test_function", vec![], None, None) + .simulate_from_contract_id("", "test_function", vec![], None) .await; assert!(matches!(result, Err(SimulationError::NodeError(_)))); } @@ -945,7 +918,6 @@ mod tests { latest_ledger: 42, cost_stroops: 10, state_dependency: None, - protocol_impact: None, } }