From 69862a193ca1651a0f3c744aeb1f027e285f0a41 Mon Sep 17 00:00:00 2001 From: 0xzrf Date: Fri, 6 Mar 2026 00:45:50 +0530 Subject: [PATCH 1/8] feature(rpc): Adds cheatcode rpc methods to enable/disable other cheatcodes --- crates/core/src/error.rs | 12 + crates/core/src/rpc/mod.rs | 37 ++- crates/core/src/rpc/surfnet_cheatcodes.rs | 266 +++++++++++++++++++++- crates/core/src/runloops/mod.rs | 1 + crates/core/src/tests/helpers.rs | 3 +- crates/core/src/tests/integration.rs | 154 ++++++++++++- crates/types/src/types.rs | 164 +++++++++++++ 7 files changed, 629 insertions(+), 8 deletions(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index 60de4047..4cb4561a 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -104,6 +104,18 @@ impl SurfpoolError { Self(error) } + pub fn disable_cheatcode(e: String) -> Self { + let mut error = Error::invalid_request(); + error.data = Some(json!(format!("Unable to disable cheatcode: {}", e))); + Self(error) + } + + pub fn enable_cheatcode(e: String) -> Self { + let mut error = Error::invalid_request(); + error.data = Some(json!(format!("Unable to enable cheatcode: {}", e))); + Self(error) + } + pub fn set_account(pubkey: Pubkey, e: T) -> Self where T: ToString, diff --git a/crates/core/src/rpc/mod.rs b/crates/core/src/rpc/mod.rs index ccc2d468..9b6880d8 100644 --- a/crates/core/src/rpc/mod.rs +++ b/crates/core/src/rpc/mod.rs @@ -1,5 +1,3 @@ -use std::{future::Future, sync::Arc}; - use blake3::Hash; use crossbeam_channel::Sender; use jsonrpc_core::{ @@ -9,7 +7,13 @@ use jsonrpc_core::{ }; use jsonrpc_pubsub::{PubSubMetadata, Session}; use solana_clock::Slot; -use surfpool_types::{SimnetCommand, SimnetEvent, types::RpcConfig}; +use std::{ + future::Future, + sync::{Arc, Mutex}, +}; +use surfpool_types::{ + CheatcodeConfig, RpcCheatcodes, SimnetCommand, SimnetEvent, types::RpcConfig, +}; use crate::{ PluginManagerCommand, @@ -48,6 +52,7 @@ pub struct RunloopContext { pub plugin_manager_commands_tx: Sender, pub remote_rpc_client: Option, pub rpc_config: RpcConfig, + pub cheatcode_config: Arc>, } pub struct SurfnetRpcContext { @@ -113,6 +118,7 @@ pub struct SurfpoolMiddleware { pub plugin_manager_commands_tx: Sender, pub config: RpcConfig, pub remote_rpc_client: Option, + pub cheatcode_config: Arc>, } impl SurfpoolMiddleware { @@ -129,6 +135,7 @@ impl SurfpoolMiddleware { plugin_manager_commands_tx: plugin_manager_commands_tx.clone(), config: config.clone(), remote_rpc_client: remote_rpc_client.clone(), + cheatcode_config: CheatcodeConfig::new(), } } } @@ -171,7 +178,30 @@ impl Middleware> for SurfpoolMiddleware { plugin_manager_commands_tx: self.plugin_manager_commands_tx.clone(), remote_rpc_client: self.remote_rpc_client.clone(), rpc_config: self.config.clone(), + cheatcode_config: self.cheatcode_config.clone(), }); + + if RpcCheatcodes::try_from(method_name.as_str()).is_ok() + && let Some(meta_val) = meta.clone() + && meta_val + .cheatcode_config + .lock() + .unwrap() // this is okay since only on_request, disable_cheatcode and enable_cheatcode only use it, the rpc method being called after on_request + .is_cheatcode_disabled(&method_name) + { + let error = Response::from( + Error { + code: ErrorCode::InvalidRequest, + message: format!("Cheatsheet rpc method: {method_name} is currently disabled"), + data: None, + }, + None, + ); + warn!("Request rejected due to cheatsheet being disabled"); + + return Either::Left(Box::pin(async move { Some(error) })); + } + Either::Left(Box::pin(next(request, meta).map(move |res| { if let Some(Response::Single(output)) = &res { if let jsonrpc_core::Output::Failure(failure) = output { @@ -222,6 +252,7 @@ impl Middleware> for SurfpoolWebsocketMiddleware { plugin_manager_commands_tx: self.surfpool_middleware.plugin_manager_commands_tx.clone(), remote_rpc_client: self.surfpool_middleware.remote_rpc_client.clone(), rpc_config: self.surfpool_middleware.config.clone(), + cheatcode_config: self.surfpool_middleware.cheatcode_config.clone(), }; let session = meta .as_ref() diff --git a/crates/core/src/rpc/surfnet_cheatcodes.rs b/crates/core/src/rpc/surfnet_cheatcodes.rs index 930c83c2..3c95bb32 100644 --- a/crates/core/src/rpc/surfnet_cheatcodes.rs +++ b/crates/core/src/rpc/surfnet_cheatcodes.rs @@ -15,8 +15,8 @@ use solana_transaction::versioned::VersionedTransaction; use spl_associated_token_account_interface::address::get_associated_token_address_with_program_id; use surfpool_types::{ AccountSnapshot, ClockCommand, ExportSnapshotConfig, GetStreamedAccountsResponse, - GetSurfnetInfoResponse, Idl, ResetAccountConfig, RpcProfileResultConfig, Scenario, - SimnetCommand, SimnetEvent, StreamAccountConfig, UiKeyedProfileResult, + GetSurfnetInfoResponse, Idl, ResetAccountConfig, RpcCheatcodes, RpcProfileResultConfig, + Scenario, SimnetCommand, SimnetEvent, StreamAccountConfig, UiKeyedProfileResult, types::{AccountUpdate, SetSomeAccount, SupplyUpdate, TokenAccountUpdate, UuidOrSignature}, }; @@ -179,6 +179,167 @@ pub trait SurfnetCheatcodes { update: AccountUpdate, ) -> BoxFuture>>; + /// Enables one or more Surfpool cheatcode RPC methods for the current session. + /// + /// This method allows developers to re-enable cheatcode methods that were previously disabled. + /// Each cheatcode name must match a valid `surfnet_*` RPC method (e.g. `surfnet_setAccount`, `surfnet_timeTravel`). + /// + /// ## Parameters + /// - `cheatcodes`: A list of cheatcode method names to enable, as strings (e.g. `["surfnet_setAccount", "surfnet_timeTravel"]`). + /// + /// ## Returns + /// A `RpcResponse<()>` indicating whether the enable operation was successful. + /// + /// ## Example Request + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "id": 1, + /// "method": "surfnet_enableCheatcode", + /// "params": [["surfnet_setAccount", "surfnet_timeTravel"]] + /// } + /// ``` + /// + /// ## Example Response + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "result": {}, + /// "id": 1 + /// } + /// ``` + /// + /// # Notes + /// Invalid cheatcode names return an error. Use `surfnet_disableCheatcode` to disable methods and `surfnet_lockout` to allow disabling `surfnet_enableCheatcode` and `surfnet_disableCheatcode` themselves. + /// + /// # See Also + /// - `surfnet_disableCheatcode`, `surfnet_disableAllCheatcodes`, `surfnet_lockout` + #[rpc(meta, name = "surfnet_enableCheatcode")] + fn enable_cheatcode( + &self, + meta: Self::Metadata, + cheatcodes: Vec, + ) -> Result>; + + /// Disables one or more Surfpool cheatcode RPC methods for the current session. + /// + /// This method allows developers to turn off specific cheatcode methods so they are no longer callable. + /// Each cheatcode name must match a valid `surfnet_*` RPC method. When lockout is not enabled, + /// `surfnet_enableCheatcode` and `surfnet_disableCheatcode` cannot be disabled. + /// + /// ## Parameters + /// - `cheatcodes`: A list of cheatcode method names to disable, as strings (e.g. `["surfnet_setAccount"]`). + /// + /// ## Returns + /// A `RpcResponse<()>` indicating whether the disable operation was successful. + /// + /// ## Example Request + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "id": 1, + /// "method": "surfnet_disableCheatcode", + /// "params": [["surfnet_setAccount", "surfnet_timeTravel"]] + /// } + /// ``` + /// + /// ## Example Response + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "result": {}, + /// "id": 1 + /// } + /// ``` + /// + /// # Notes + /// Call `surfnet_lockout` first if you need to disable `surfnet_enableCheatcode` or `surfnet_disableCheatcode`. + /// + /// # See Also + /// - `surfnet_enableCheatcode`, `surfnet_disableAllCheatcodes`, `surfnet_lockout` + #[rpc(meta, name = "surfnet_disableCheatcode")] + fn disable_cheatcode( + &self, + meta: Self::Metadata, + cheatcodes: Vec, + ) -> Result>; + + /// Enables "lockout" mode so that `surfnet_enableCheatcode` and `surfnet_disableCheatcode` can be disabled. + /// + /// Once lockout is enabled, those two meta-cheatcode methods may be disabled via `surfnet_disableCheatcode`, + /// allowing a session to lock down further modification of the cheatcode set. + /// + /// ## Parameters + /// None. + /// + /// ## Returns + /// A `RpcResponse<()>` indicating success. + /// + /// ## Example Request + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "id": 1, + /// "method": "surfnet_lockout", + /// "params": [] + /// } + /// ``` + /// + /// ## Example Response + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "result": {}, + /// "id": 1 + /// } + /// ``` + /// + /// # Notes + /// Lockout cannot be turned off once enabled for the current session. + /// + /// # See Also + /// - `surfnet_enableCheatcode`, `surfnet_disableCheatcode`, `surfnet_disableAllCheatcodes` + #[rpc(meta, name = "surfnet_lockout")] + fn lockout(&self, meta: Self::Metadata) -> Result>; + + /// Disables all Surfpool cheatcode RPC methods for the current session. + /// + /// This method turns off every cheatcode at once. After calling it, no `surfnet_*` cheatcode methods + /// are callable until re-enabled via `surfnet_enableCheatcode`. + /// + /// ## Parameters + /// None. + /// + /// ## Returns + /// A `RpcResponse<()>` indicating success. + /// + /// ## Example Request + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "id": 1, + /// "method": "surfnet_disableAllCheatcodes", + /// "params": [] + /// } + /// ``` + /// + /// ## Example Response + /// ```json + /// { + /// "jsonrpc": "2.0", + /// "result": {}, + /// "id": 1 + /// } + /// ``` + /// + /// # Notes + /// Does not require lockout to be enabled; it disables all cheatcodes including + /// `surfnet_enableCheatcode` and `surfnet_disableCheatcode`. + /// + /// # See Also + /// - `surfnet_enableCheatcode`, `surfnet_disableCheatcode`, `surfnet_lockout` + #[rpc(meta, name = "surfnet_disableAllCheatcodes")] + fn disable_all_cheatcodes(&self, meta: Self::Metadata) -> Result>; /// A "cheat code" method for developers to set or update a token account in Surfpool. /// /// This method allows developers to set or update various properties of a token account, @@ -1201,6 +1362,107 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { }) } + fn disable_cheatcode( + &self, + meta: Self::Metadata, + cheatcodes: Vec, + ) -> Result> { + let svm_locker = match meta.get_svm_locker() { + Ok(locker) => locker, + Err(e) => return Err(e.into()), + }; + if let Some(runloop_ctx) = meta { + let mut cheatcode_ctx = runloop_ctx.cheatcode_config.lock().unwrap(); + + for cheatcode in cheatcodes { + if !cheatcode_ctx.lockout + && (cheatcode.eq(&String::from(RpcCheatcodes::EnableCheatcode)) + || cheatcode.eq(&String::from(RpcCheatcodes::DisableCheatcode))) + { + return Err(SurfpoolError::disable_cheatcode( + "Cannot disable surfnet_enableCheatcode or surfnet_disableCheatcode rpc method when lockout is not enabledd".to_string(), + ) + .into()); + } + debug!("disabling cheatcode: {cheatcode}"); + if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { + return Err(SurfpoolError::disable_cheatcode( + "Invalid cheatcode rpc method".to_string(), + ) + .into()); + } + + if let Err(e) = cheatcode_ctx.disable_cheatcode(&cheatcode) { + return Err(SurfpoolError::disable_cheatcode(e).into()); + } + } + } + + Ok(RpcResponse { + value: (), + context: RpcResponseContext::new(svm_locker.get_latest_absolute_slot()), + }) + } + + fn enable_cheatcode( + &self, + meta: Self::Metadata, + cheatcodes: Vec, + ) -> Result> { + let svm_locker = match meta.get_svm_locker() { + Ok(locker) => locker, + Err(e) => return Err(e.into()), + }; + if let Some(runloop_ctx) = meta { + let cheatcode_ctx = runloop_ctx.cheatcode_config; + + for ref cheatcode in cheatcodes { + debug!("enabling cheatcode: {cheatcode}"); + if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { + return Err(SurfpoolError::enable_cheatcode( + "Invalid cheatcode rpc method".to_string(), + ) + .into()); + } + + if let Err(e) = cheatcode_ctx.lock().unwrap().enable_cheatcode(cheatcode) { + return Err(SurfpoolError::enable_cheatcode(e).into()); + } + } + } + + Ok(RpcResponse { + value: (), + context: RpcResponseContext::new(svm_locker.get_latest_absolute_slot()), + }) + } + + fn disable_all_cheatcodes(&self, meta: Self::Metadata) -> Result> { + if let Some(runloop_ctx) = meta { + let cheatcode_ctx = runloop_ctx.cheatcode_config; + + cheatcode_ctx.lock().unwrap().disable_all(); + } + + Ok(RpcResponse { + value: (), + context: RpcResponseContext::new(0), + }) + } + + fn lockout(&self, meta: Self::Metadata) -> Result> { + if let Some(runloop_ctx) = meta { + let cheatcode_ctx = runloop_ctx.cheatcode_config; + + cheatcode_ctx.lock().unwrap().lockout(); + } + + Ok(RpcResponse { + value: (), + context: RpcResponseContext::new(0), + }) + } + fn set_token_account( &self, meta: Self::Metadata, diff --git a/crates/core/src/runloops/mod.rs b/crates/core/src/runloops/mod.rs index 0a4252cc..ee689bdf 100644 --- a/crates/core/src/runloops/mod.rs +++ b/crates/core/src/runloops/mod.rs @@ -1140,6 +1140,7 @@ async fn start_ws_rpc_server_runloop( .clone(), remote_rpc_client: middleware.remote_rpc_client.clone(), rpc_config: middleware.config.clone(), + cheatcode_config: middleware.cheatcode_config.clone(), }; Some(SurfpoolWebsocketMeta::new( runloop_context, diff --git a/crates/core/src/tests/helpers.rs b/crates/core/src/tests/helpers.rs index 8cf3061f..7008c2f7 100644 --- a/crates/core/src/tests/helpers.rs +++ b/crates/core/src/tests/helpers.rs @@ -5,7 +5,7 @@ use crossbeam_channel::Sender; use solana_clock::Clock; use solana_epoch_info::EpochInfo; use solana_transaction::versioned::VersionedTransaction; -use surfpool_types::{RpcConfig, SimnetCommand}; +use surfpool_types::{CheatcodeConfig, RpcConfig, SimnetCommand}; use crate::{ rpc::RunloopContext, @@ -67,6 +67,7 @@ where svm_locker: SurfnetSvmLocker::new(surfnet_svm), remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }, rpc, } diff --git a/crates/core/src/tests/integration.rs b/crates/core/src/tests/integration.rs index c2f635ae..5fda1556 100644 --- a/crates/core/src/tests/integration.rs +++ b/crates/core/src/tests/integration.rs @@ -32,8 +32,9 @@ use solana_system_interface::{ }; use solana_transaction::{Transaction, versioned::VersionedTransaction}; use surfpool_types::{ - DEFAULT_SLOT_TIME_MS, Idl, RpcProfileDepth, RpcProfileResultConfig, SimnetCommand, SimnetEvent, - SurfpoolConfig, UiAccountChange, UiAccountProfileState, UiKeyedProfileResult, + CheatcodeConfig, DEFAULT_SLOT_TIME_MS, Idl, RpcCheatcodes, RpcProfileDepth, + RpcProfileResultConfig, SimnetCommand, SimnetEvent, SurfpoolConfig, UiAccountChange, + UiAccountProfileState, UiKeyedProfileResult, types::{ BlockProductionMode, RpcConfig, SimnetConfig, SubgraphConfig, TransactionStatusEvent, UuidOrSignature, @@ -796,6 +797,7 @@ async fn test_surfnet_estimate_compute_units(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Test with None tag @@ -1048,6 +1050,140 @@ async fn test_surfnet_estimate_compute_units(test_type: TestType) { assert!(found_cu_event, "Did not find CU estimation SimnetEvent"); } +#[test_case(TestType::sqlite(); "with on-disk sqlite db")] +#[test_case(TestType::in_memory(); "with in-memory sqlite db")] +#[test_case(TestType::no_db(); "with no db")] +#[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] +fn test_enable_and_disable_cheatcodes(test_type: TestType) { + let rpc_server = SurfnetCheatcodesRpc; + let (svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); + let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance); + let (simnet_cmd_tx, _simnet_cmd_rx) = crossbeam_unbounded::(); + let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); + let disable_cheatcode_method: String = RpcCheatcodes::GetActiveIdl.into(); + let invalid_cheatcode = "invalidCheatcode".to_string(); + + let runloop_context = RunloopContext { + id: None, + svm_locker: svm_locker_for_context.clone(), + simnet_commands_tx: simnet_cmd_tx, + plugin_manager_commands_tx: plugin_cmd_tx, + remote_rpc_client: None, + rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), + }; + + let disable_cheatcode_result = rpc_server.disable_cheatcode( + Some(runloop_context.clone()), + vec![disable_cheatcode_method.to_string()], + ); + + // test whether enabling and disabling cheatcodes work + assert!( + disable_cheatcode_result.is_ok(), + "Cheatcode disable failed: {:?}", + disable_cheatcode_result.err() + ); + + assert!( + runloop_context + .cheatcode_config + .lock() + .unwrap() + .is_cheatcode_disabled(&disable_cheatcode_method.to_string()), + "Expected {} rpc method to be disabled", + disable_cheatcode_method + ); + + let enable_cheatcode_result = rpc_server.enable_cheatcode( + Some(runloop_context.clone()), + vec![disable_cheatcode_method.to_string()], + ); + + assert!( + enable_cheatcode_result.is_ok(), + "Cheatcode enable failed: {:?}", + disable_cheatcode_result.err() + ); + + assert!( + !runloop_context + .cheatcode_config + .lock() + .unwrap() + .is_cheatcode_disabled(&disable_cheatcode_method.to_string()), + "Expected {} rpc method to be enabled", + disable_cheatcode_method + ); + + // Test handling an exeptional case where param provides an invalid cheatcode + let invalid_cheatcode_disable_result = + rpc_server.enable_cheatcode(Some(runloop_context.clone()), vec![invalid_cheatcode]); + + assert!( + invalid_cheatcode_disable_result.is_err(), + "Expected passing an invalid cheatcode to be disabled to result in Error" + ); + + // cheatcode_config.lockout tests + // disabling enableCheatcode and disableCheatcode should result in error when lockout == false + let disable_cheatcode_enable_rpc_method_result = rpc_server.disable_cheatcode( + Some(runloop_context.clone()), + vec![RpcCheatcodes::EnableCheatcode.into()], + ); + let disable_cheatcode_disable_rpc_method_result = rpc_server.disable_cheatcode( + Some(runloop_context.clone()), + vec![RpcCheatcodes::DisableCheatcode.into()], + ); + assert!( + disable_cheatcode_enable_rpc_method_result.is_err() + && disable_cheatcode_disable_rpc_method_result.is_err(), + "Expected disable_cheatcode to fail when trying to disable surfnet_enableCheatcode rpc method when lockout == false" + ); + + let enable_lockout_result = rpc_server.lockout(Some(runloop_context.clone())); + + assert!( + enable_lockout_result.is_ok(), + "Expected lockout rpc method to succeed" + ); + + assert!( + runloop_context.cheatcode_config.lock().unwrap().lockout, + "Expected lockout to be true" + ); + + let disable_cheatcode_control_rpc_method_succeed_result = rpc_server.disable_cheatcode( + Some(runloop_context.clone()), + vec![RpcCheatcodes::EnableCheatcode.into()], + ); + + assert!( + disable_cheatcode_control_rpc_method_succeed_result.is_ok(), + "Expected disable_cheatcode to pass when trying to disable surfnet_enableCheatcode rpc method when lockout == true" + ); + + // Disable all cheatcode tests + // NOTE: Testing whether the cheatcode rpc method are disabled after the call the disable_all_cheatcodes is not possible as + // filtering happens in the on_request middleware + let disable_all_result = rpc_server.disable_all_cheatcodes(Some(runloop_context.clone())); + + assert!( + disable_all_result.is_ok(), + "Expected disable all rpc method to work without fail" + ); + + assert!( + runloop_context + .cheatcode_config + .lock() + .unwrap() + .filter + .eq(&surfpool_types::CheatcodeFilter::All), + "Expected cheatcode filter to have the CheatcodeFilter::All variant" + ); +} + #[test_case(TestType::sqlite(); "with on-disk sqlite db")] #[test_case(TestType::in_memory(); "with in-memory sqlite db")] #[test_case(TestType::no_db(); "with no db")] @@ -1090,6 +1226,7 @@ async fn test_get_transaction_profile(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Test 1: Profile a transaction with a tag and retrieve by UUID @@ -1289,6 +1426,7 @@ fn test_register_and_get_idl_without_slot(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Test 1: Register IDL without slot @@ -1344,6 +1482,7 @@ fn test_register_and_get_idl_with_slot(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Test 1: Register IDL with slot @@ -1425,6 +1564,7 @@ async fn test_register_and_get_same_idl_with_different_slots(test_type: TestType plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Step 1: Register IDL v1 at slot_1 @@ -3105,6 +3245,7 @@ async fn test_get_local_signatures_without_limit(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; let payer = Keypair::new(); @@ -3209,6 +3350,7 @@ async fn test_get_local_signatures_with_limit(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; let payer = Keypair::new(); @@ -3416,6 +3558,7 @@ fn test_time_travel_resume_paused_clock(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Get initial epoch info @@ -3497,6 +3640,7 @@ fn test_time_travel_absolute_timestamp(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; let clock = Clock { @@ -3579,6 +3723,7 @@ fn test_time_travel_absolute_slot(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; let clock = Clock { @@ -3655,6 +3800,7 @@ fn test_time_travel_absolute_epoch(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; let clock = Clock { @@ -4388,6 +4534,7 @@ fn test_reset_network_time_travel_timestamp(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Calculate a target timestamp in the future @@ -4440,6 +4587,7 @@ fn test_reset_network_time_travel_slot(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Do an initial reset to ensure we start from slot 0 @@ -4496,6 +4644,7 @@ fn test_reset_network_time_travel_epoch(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Do an initial reset to ensure we start from epoch 0 @@ -6980,6 +7129,7 @@ async fn test_profile_transaction_does_not_mutate_state(test_type: TestType) { plugin_manager_commands_tx: plugin_cmd_tx, remote_rpc_client: None, rpc_config: RpcConfig::default(), + cheatcode_config: CheatcodeConfig::new(), }; // Profile the transaction multiple times to ensure no state leakage diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs index 7cdc3f8a..857315a5 100644 --- a/crates/types/src/types.rs +++ b/crates/types/src/types.rs @@ -4,6 +4,7 @@ use std::{ fmt, path::PathBuf, str::FromStr, + sync::{Arc, Mutex}, }; use blake3::Hash; @@ -1257,6 +1258,169 @@ impl RunbookExecutionStatusReport { } } +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CheatcodeConfig { + pub lockout: bool, // if true, allows disabling even the `surfnet_enableCheatcodes`/`surfnetdisableCheatcodes` methods + pub filter: CheatcodeFilter, +} + +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CheatcodeFilter { + #[default] + All, + List(Vec), // disables cheatcodes in a named list +} + +impl CheatcodeConfig { + pub fn new() -> Arc> { + Arc::new(Mutex::new(CheatcodeConfig { + lockout: false, + filter: CheatcodeFilter::List(vec![]), + })) + } + + pub fn lockout(&mut self) { + self.lockout = true; + } + + pub fn disable_all(&mut self) { + self.filter = CheatcodeFilter::All; + } + + pub fn disable_cheatcode(&mut self, cheatcode: &String) -> Result<(), String> { + if !self.lockout + && (cheatcode.eq("surfpool_enableCheatcode") + || cheatcode.eq("surfpool_enableCheatcode")) + { + return Err("Cannot disable surfpool_enableCheatcode and surfpool_enableCheatcode while lockout is is false".to_string()); + } + + if let CheatcodeFilter::List(list) = &mut self.filter { + if !list.contains(cheatcode) { + list.push(cheatcode.to_string()); + Ok(()) + } else { + Err("Cheatcode already disabled".to_string()) + } + } else { + Err("All cheatcodes disabled".to_string()) + } + } + pub fn enable_cheatcode(&mut self, cheatcode: &str) -> Result<(), String> { + if let CheatcodeFilter::List(list) = &mut self.filter { + if let Some(pos) = list.iter().position(|c| c == cheatcode) { + list.remove(pos); + Ok(()) + } else { + Err("Cheatcode isn't disabled".to_string()) + } + } else { + Err("All cheatcodes are disabled".to_string()) + } + } + + pub fn is_cheatcode_disabled(&self, cheatcode: &String) -> bool { + match &self.filter { + CheatcodeFilter::List(list) => list.contains(cheatcode), + CheatcodeFilter::All => true, + } + } +} + +pub enum RpcCheatcodes { + SetAccount, + EnableCheatcode, + DisableCheatcode, + SetTokenAccount, + CloneProgramAccount, + ProfileTransaction, + GetProfileResultsByTag, + SetSupply, + SetProgramAuthority, + GetTransactionProfile, + RegisterIdl, + GetActiveIdl, + GetLocalSignatures, + TimeTravel, + PauseClock, + ResumeClock, + ResetAccount, + ResetNetwork, + ExportSnapshot, + StreamAccount, + GetStreamedAccounts, + GetSurfnetInfo, + WriteProgram, + RegisterScenario, +} + +impl From for String { + fn from(value: RpcCheatcodes) -> Self { + match value { + RpcCheatcodes::SetAccount => String::from("surfnet_setAccount"), + RpcCheatcodes::EnableCheatcode => String::from("surfnet_enableCheatcode"), + RpcCheatcodes::DisableCheatcode => String::from("surfnet_disableCheatcode"), + RpcCheatcodes::SetTokenAccount => String::from("surfnet_setTokenAccount"), + RpcCheatcodes::CloneProgramAccount => String::from("surfnet_cloneProgramAccount"), + RpcCheatcodes::ProfileTransaction => String::from("surfnet_profileTransaction"), + RpcCheatcodes::GetProfileResultsByTag => String::from("surfnet_getProfileResultsByTag"), + RpcCheatcodes::SetSupply => String::from("surfnet_setSupply"), + RpcCheatcodes::SetProgramAuthority => String::from("surfnet_setProgramAuthority"), + RpcCheatcodes::GetTransactionProfile => String::from("surfnet_getTransactionProfile"), + RpcCheatcodes::RegisterIdl => String::from("surfnet_registerIdl"), + RpcCheatcodes::GetActiveIdl => String::from("surfnet_getActiveIdl"), + RpcCheatcodes::GetLocalSignatures => String::from("surfnet_getLocalSignatures"), + RpcCheatcodes::TimeTravel => String::from("surfnet_timeTravel"), + RpcCheatcodes::PauseClock => String::from("surfnet_pauseClock"), + RpcCheatcodes::ResumeClock => String::from("surfnet_resumeClock"), + RpcCheatcodes::ResetAccount => String::from("surfnet_resetAccount"), + RpcCheatcodes::ResetNetwork => String::from("surfnet_resetNetwork"), + RpcCheatcodes::ExportSnapshot => String::from("surfnet_exportSnapshot"), + RpcCheatcodes::StreamAccount => String::from("surfnet_streamAccount"), + RpcCheatcodes::GetStreamedAccounts => String::from("surfnet_getStreamedAccounts"), + RpcCheatcodes::GetSurfnetInfo => String::from("surfnet_getSurfnetInfo"), + RpcCheatcodes::WriteProgram => String::from("surfnet_writeProgram"), + RpcCheatcodes::RegisterScenario => String::from("surfnet_registerScenario"), + } + } +} + +impl TryFrom<&str> for RpcCheatcodes { + type Error = String; + + fn try_from(value: &str) -> Result { + match value { + "surfnet_setAccount" => Ok(RpcCheatcodes::SetAccount), + "surfnet_enableCheatcode" => Ok(RpcCheatcodes::EnableCheatcode), + "surfnet_disableCheatcode" => Ok(RpcCheatcodes::DisableCheatcode), + "surfnet_setTokenAccount" => Ok(RpcCheatcodes::SetTokenAccount), + "surfnet_cloneProgramAccount" => Ok(RpcCheatcodes::CloneProgramAccount), + "surfnet_profileTransaction" => Ok(RpcCheatcodes::ProfileTransaction), + "surfnet_getProfileResultsByTag" => Ok(RpcCheatcodes::GetProfileResultsByTag), + "surfnet_setSupply" => Ok(RpcCheatcodes::SetSupply), + "surfnet_setProgramAuthority" => Ok(RpcCheatcodes::SetProgramAuthority), + "surfnet_getTransactionProfile" => Ok(RpcCheatcodes::GetTransactionProfile), + "surfnet_registerIdl" => Ok(RpcCheatcodes::RegisterIdl), + "surfnet_getActiveIdl" => Ok(RpcCheatcodes::GetActiveIdl), + "surfnet_getLocalSignatures" => Ok(RpcCheatcodes::GetLocalSignatures), + "surfnet_timeTravel" => Ok(RpcCheatcodes::TimeTravel), + "surfnet_pauseClock" => Ok(RpcCheatcodes::PauseClock), + "surfnet_resumeClock" => Ok(RpcCheatcodes::ResumeClock), + "surfnet_resetAccount" => Ok(RpcCheatcodes::ResetAccount), + "surfnet_resetNetwork" => Ok(RpcCheatcodes::ResetNetwork), + "surfnet_exportSnapshot" => Ok(RpcCheatcodes::ExportSnapshot), + "surfnet_streamAccount" => Ok(RpcCheatcodes::StreamAccount), + "surfnet_getStreamedAccounts" => Ok(RpcCheatcodes::GetStreamedAccounts), + "surfnet_getSurfnetInfo" => Ok(RpcCheatcodes::GetSurfnetInfo), + "surfnet_writeProgram" => Ok(RpcCheatcodes::WriteProgram), + "surfnet_registerScenario" => Ok(RpcCheatcodes::RegisterScenario), + _ => Err(format!("unknown cheatcode method: {}", value)), + } + } +} + #[cfg(test)] mod tests { use serde_json::json; From 88578c660af52aad180e5f1e5293f32c58a191af Mon Sep 17 00:00:00 2001 From: 0xzrf Date: Fri, 6 Mar 2026 01:33:31 +0530 Subject: [PATCH 2/8] added cargo +nightly fmt --all -- --check fix --- crates/core/src/rpc/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/core/src/rpc/mod.rs b/crates/core/src/rpc/mod.rs index 9b6880d8..5ee55bbd 100644 --- a/crates/core/src/rpc/mod.rs +++ b/crates/core/src/rpc/mod.rs @@ -1,3 +1,8 @@ +use std::{ + future::Future, + sync::{Arc, Mutex}, +}; + use blake3::Hash; use crossbeam_channel::Sender; use jsonrpc_core::{ @@ -7,10 +12,6 @@ use jsonrpc_core::{ }; use jsonrpc_pubsub::{PubSubMetadata, Session}; use solana_clock::Slot; -use std::{ - future::Future, - sync::{Arc, Mutex}, -}; use surfpool_types::{ CheatcodeConfig, RpcCheatcodes, SimnetCommand, SimnetEvent, types::RpcConfig, }; From d620266f8c7650cdb2c40befbdf67cedb51a29f7 Mon Sep 17 00:00:00 2001 From: 0xzrf Date: Wed, 11 Mar 2026 14:13:42 +0530 Subject: [PATCH 3/8] pr improvement --- crates/core/src/rpc/surfnet_cheatcodes.rs | 202 ++++++----------- crates/core/src/tests/integration.rs | 251 ++++++++++++++++------ 2 files changed, 255 insertions(+), 198 deletions(-) diff --git a/crates/core/src/rpc/surfnet_cheatcodes.rs b/crates/core/src/rpc/surfnet_cheatcodes.rs index 3c95bb32..bb9475c0 100644 --- a/crates/core/src/rpc/surfnet_cheatcodes.rs +++ b/crates/core/src/rpc/surfnet_cheatcodes.rs @@ -14,9 +14,10 @@ use solana_system_interface::program as system_program; use solana_transaction::versioned::VersionedTransaction; use spl_associated_token_account_interface::address::get_associated_token_address_with_program_id; use surfpool_types::{ - AccountSnapshot, ClockCommand, ExportSnapshotConfig, GetStreamedAccountsResponse, - GetSurfnetInfoResponse, Idl, ResetAccountConfig, RpcCheatcodes, RpcProfileResultConfig, - Scenario, SimnetCommand, SimnetEvent, StreamAccountConfig, UiKeyedProfileResult, + AccountSnapshot, CheatcodeControlConfig, CheatcodeFilter, ClockCommand, ExportSnapshotConfig, + GetStreamedAccountsResponse, GetSurfnetInfoResponse, Idl, ResetAccountConfig, RpcCheatcodes, + RpcProfileResultConfig, Scenario, SimnetCommand, SimnetEvent, StreamAccountConfig, + UiKeyedProfileResult, types::{AccountUpdate, SetSomeAccount, SupplyUpdate, TokenAccountUpdate, UuidOrSignature}, }; @@ -218,7 +219,7 @@ pub trait SurfnetCheatcodes { fn enable_cheatcode( &self, meta: Self::Metadata, - cheatcodes: Vec, + cheatcodes_filter: CheatcodeFilter, ) -> Result>; /// Disables one or more Surfpool cheatcode RPC methods for the current session. @@ -261,85 +262,10 @@ pub trait SurfnetCheatcodes { fn disable_cheatcode( &self, meta: Self::Metadata, - cheatcodes: Vec, + cheatcodes_filter: CheatcodeFilter, + lockout: Option, ) -> Result>; - /// Enables "lockout" mode so that `surfnet_enableCheatcode` and `surfnet_disableCheatcode` can be disabled. - /// - /// Once lockout is enabled, those two meta-cheatcode methods may be disabled via `surfnet_disableCheatcode`, - /// allowing a session to lock down further modification of the cheatcode set. - /// - /// ## Parameters - /// None. - /// - /// ## Returns - /// A `RpcResponse<()>` indicating success. - /// - /// ## Example Request - /// ```json - /// { - /// "jsonrpc": "2.0", - /// "id": 1, - /// "method": "surfnet_lockout", - /// "params": [] - /// } - /// ``` - /// - /// ## Example Response - /// ```json - /// { - /// "jsonrpc": "2.0", - /// "result": {}, - /// "id": 1 - /// } - /// ``` - /// - /// # Notes - /// Lockout cannot be turned off once enabled for the current session. - /// - /// # See Also - /// - `surfnet_enableCheatcode`, `surfnet_disableCheatcode`, `surfnet_disableAllCheatcodes` - #[rpc(meta, name = "surfnet_lockout")] - fn lockout(&self, meta: Self::Metadata) -> Result>; - - /// Disables all Surfpool cheatcode RPC methods for the current session. - /// - /// This method turns off every cheatcode at once. After calling it, no `surfnet_*` cheatcode methods - /// are callable until re-enabled via `surfnet_enableCheatcode`. - /// - /// ## Parameters - /// None. - /// - /// ## Returns - /// A `RpcResponse<()>` indicating success. - /// - /// ## Example Request - /// ```json - /// { - /// "jsonrpc": "2.0", - /// "id": 1, - /// "method": "surfnet_disableAllCheatcodes", - /// "params": [] - /// } - /// ``` - /// - /// ## Example Response - /// ```json - /// { - /// "jsonrpc": "2.0", - /// "result": {}, - /// "id": 1 - /// } - /// ``` - /// - /// # Notes - /// Does not require lockout to be enabled; it disables all cheatcodes including - /// `surfnet_enableCheatcode` and `surfnet_disableCheatcode`. - /// - /// # See Also - /// - `surfnet_enableCheatcode`, `surfnet_disableCheatcode`, `surfnet_lockout` - #[rpc(meta, name = "surfnet_disableAllCheatcodes")] - fn disable_all_cheatcodes(&self, meta: Self::Metadata) -> Result>; /// A "cheat code" method for developers to set or update a token account in Surfpool. /// /// This method allows developers to set or update various properties of a token account, @@ -1365,35 +1291,51 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { fn disable_cheatcode( &self, meta: Self::Metadata, - cheatcodes: Vec, + cheatcodes_filter: CheatcodeFilter, + control_config: Option, ) -> Result> { let svm_locker = match meta.get_svm_locker() { Ok(locker) => locker, Err(e) => return Err(e.into()), }; + + let CheatcodeControlConfig { lockout } = control_config.unwrap_or_default(); + let lockout = lockout.unwrap_or_default(); + if let Some(runloop_ctx) = meta { let mut cheatcode_ctx = runloop_ctx.cheatcode_config.lock().unwrap(); - for cheatcode in cheatcodes { - if !cheatcode_ctx.lockout - && (cheatcode.eq(&String::from(RpcCheatcodes::EnableCheatcode)) - || cheatcode.eq(&String::from(RpcCheatcodes::DisableCheatcode))) - { - return Err(SurfpoolError::disable_cheatcode( - "Cannot disable surfnet_enableCheatcode or surfnet_disableCheatcode rpc method when lockout is not enabledd".to_string(), - ) - .into()); - } - debug!("disabling cheatcode: {cheatcode}"); - if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { - return Err(SurfpoolError::disable_cheatcode( - "Invalid cheatcode rpc method".to_string(), - ) - .into()); + match cheatcodes_filter { + CheatcodeFilter::All(all) => { + if all.ne("all") { + return Err(SurfpoolError::disable_cheatcode( + "Invalid optioin provided for disabling all cheatcodes. Try using \"all\"".to_string(), + ) + .into()); + } + + cheatcode_ctx.disable_all(lockout); } + CheatcodeFilter::List(cheatdcodes) => { + for cheatcode in cheatdcodes { + if !lockout && cheatcode.eq(&String::from(RpcCheatcodes::EnableCheatcode)) { + return Err(SurfpoolError::disable_cheatcode( + "Cannot disable surfnet_enableCheatcode rpc method when lockout is not enabledd".to_string(), + ) + .into()); + } + debug!("disabling cheatcode: {cheatcode}"); + if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { + return Err(SurfpoolError::disable_cheatcode( + "Invalid cheatcode rpc method".to_string(), + ) + .into()); + } - if let Err(e) = cheatcode_ctx.disable_cheatcode(&cheatcode) { - return Err(SurfpoolError::disable_cheatcode(e).into()); + if let Err(e) = cheatcode_ctx.disable_cheatcode(&cheatcode) { + return Err(SurfpoolError::disable_cheatcode(e).into()); + } + } } } } @@ -1407,7 +1349,7 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { fn enable_cheatcode( &self, meta: Self::Metadata, - cheatcodes: Vec, + cheatcodes_filter: CheatcodeFilter, ) -> Result> { let svm_locker = match meta.get_svm_locker() { Ok(locker) => locker, @@ -1415,18 +1357,32 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { }; if let Some(runloop_ctx) = meta { let cheatcode_ctx = runloop_ctx.cheatcode_config; + match cheatcodes_filter { + CheatcodeFilter::All(all) => { + if all.ne("all") { + return Err(SurfpoolError::enable_cheatcode( + "Invalid optioin provided for enabling all cheatcodes. Try using \"all\"".to_string(), + ) + .into()); + } - for ref cheatcode in cheatcodes { - debug!("enabling cheatcode: {cheatcode}"); - if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { - return Err(SurfpoolError::enable_cheatcode( - "Invalid cheatcode rpc method".to_string(), - ) - .into()); + // we probably don't need to check whether lockout == true because surfnet_enableCheatcode won't be called if it's disabled + cheatcode_ctx.lock().unwrap().filter = CheatcodeFilter::List(vec![]); } + CheatcodeFilter::List(cheatcodes) => { + for ref cheatcode in cheatcodes { + debug!("enabling cheatcode: {cheatcode}"); + if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { + return Err(SurfpoolError::enable_cheatcode( + "Invalid cheatcode rpc method".to_string(), + ) + .into()); + } - if let Err(e) = cheatcode_ctx.lock().unwrap().enable_cheatcode(cheatcode) { - return Err(SurfpoolError::enable_cheatcode(e).into()); + if let Err(e) = cheatcode_ctx.lock().unwrap().enable_cheatcode(cheatcode) { + return Err(SurfpoolError::enable_cheatcode(e).into()); + } + } } } } @@ -1437,32 +1393,6 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { }) } - fn disable_all_cheatcodes(&self, meta: Self::Metadata) -> Result> { - if let Some(runloop_ctx) = meta { - let cheatcode_ctx = runloop_ctx.cheatcode_config; - - cheatcode_ctx.lock().unwrap().disable_all(); - } - - Ok(RpcResponse { - value: (), - context: RpcResponseContext::new(0), - }) - } - - fn lockout(&self, meta: Self::Metadata) -> Result> { - if let Some(runloop_ctx) = meta { - let cheatcode_ctx = runloop_ctx.cheatcode_config; - - cheatcode_ctx.lock().unwrap().lockout(); - } - - Ok(RpcResponse { - value: (), - context: RpcResponseContext::new(0), - }) - } - fn set_token_account( &self, meta: Self::Metadata, diff --git a/crates/core/src/tests/integration.rs b/crates/core/src/tests/integration.rs index 5fda1556..e21a7fe4 100644 --- a/crates/core/src/tests/integration.rs +++ b/crates/core/src/tests/integration.rs @@ -32,9 +32,9 @@ use solana_system_interface::{ }; use solana_transaction::{Transaction, versioned::VersionedTransaction}; use surfpool_types::{ - CheatcodeConfig, DEFAULT_SLOT_TIME_MS, Idl, RpcCheatcodes, RpcProfileDepth, - RpcProfileResultConfig, SimnetCommand, SimnetEvent, SurfpoolConfig, UiAccountChange, - UiAccountProfileState, UiKeyedProfileResult, + CheatcodeConfig, CheatcodeControlConfig, CheatcodeFilter, DEFAULT_SLOT_TIME_MS, Idl, + RpcCheatcodes, RpcProfileDepth, RpcProfileResultConfig, SimnetCommand, SimnetEvent, + SurfpoolConfig, UiAccountChange, UiAccountProfileState, UiKeyedProfileResult, types::{ BlockProductionMode, RpcConfig, SimnetConfig, SubgraphConfig, TransactionStatusEvent, UuidOrSignature, @@ -1060,8 +1060,10 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance); let (simnet_cmd_tx, _simnet_cmd_rx) = crossbeam_unbounded::(); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); - let disable_cheatcode_method: String = RpcCheatcodes::GetActiveIdl.into(); - let invalid_cheatcode = "invalidCheatcode".to_string(); + let valid_cheatcode_method: String = RpcCheatcodes::GetActiveIdl.into(); + let enable_cheatcode_method: String = RpcCheatcodes::EnableCheatcode.into(); + let disable_cheatcode_method: String = RpcCheatcodes::DisableCheatcode.into(); + let invalid_cheatcode_method = "surfnet_invalidCheatcode".to_string(); let runloop_context = RunloopContext { id: None, @@ -1073,16 +1075,16 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { cheatcode_config: CheatcodeConfig::new(), }; - let disable_cheatcode_result = rpc_server.disable_cheatcode( + // Test disable works properly + let disable_cheatcode_pass_result = rpc_server.disable_cheatcode( Some(runloop_context.clone()), - vec![disable_cheatcode_method.to_string()], + CheatcodeFilter::List(vec![valid_cheatcode_method.clone()]), + None, ); - // test whether enabling and disabling cheatcodes work assert!( - disable_cheatcode_result.is_ok(), - "Cheatcode disable failed: {:?}", - disable_cheatcode_result.err() + disable_cheatcode_pass_result.is_ok(), + "Expected surfnet_disableCheatcode to pass" ); assert!( @@ -1090,98 +1092,223 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { .cheatcode_config .lock() .unwrap() - .is_cheatcode_disabled(&disable_cheatcode_method.to_string()), - "Expected {} rpc method to be disabled", - disable_cheatcode_method + .is_cheatcode_disabled(&valid_cheatcode_method), + "Expected cheatcode to be disabled" ); - let enable_cheatcode_result = rpc_server.enable_cheatcode( + let disable_enable_cheatcode_lockout_false_fails = rpc_server.disable_cheatcode( Some(runloop_context.clone()), - vec![disable_cheatcode_method.to_string()], + CheatcodeFilter::List(vec![enable_cheatcode_method.clone()]), + None, ); + let expected_error: jsonrpc_core::Result<()> = Err(SurfpoolError::disable_cheatcode( + "Cannot disable surfnet_enableCheatcode rpc method when lockout is not enabledd" + .to_string(), + ) + .into()); + assert!( - enable_cheatcode_result.is_ok(), - "Cheatcode enable failed: {:?}", - disable_cheatcode_result.err() + disable_enable_cheatcode_lockout_false_fails.is_err(), + "Expected surfnet_disableCheatcode to fail when disabling surfnet_enableCheatcode with lockout == false" + ); + assert_eq!( + disable_enable_cheatcode_lockout_false_fails.err().unwrap(), + expected_error.err().unwrap(), + "Expected error did not match the resulting error" + ); + + let disable_cheatcode_fails_on_invalid_cheatcode = rpc_server.disable_cheatcode( + Some(runloop_context.clone()), + CheatcodeFilter::List(vec![invalid_cheatcode_method.clone()]), + None, ); + let expected_error_disable_invalid_cheatcode: jsonrpc_core::Result<()> = + Err(SurfpoolError::disable_cheatcode("Invalid cheatcode rpc method".to_string()).into()); + assert!( - !runloop_context - .cheatcode_config - .lock() - .unwrap() - .is_cheatcode_disabled(&disable_cheatcode_method.to_string()), - "Expected {} rpc method to be enabled", - disable_cheatcode_method + disable_cheatcode_fails_on_invalid_cheatcode.is_err(), + "Expected surfnet_disableCheatcode to fail on providing invalid cheatcode method" ); - // Test handling an exeptional case where param provides an invalid cheatcode - let invalid_cheatcode_disable_result = - rpc_server.enable_cheatcode(Some(runloop_context.clone()), vec![invalid_cheatcode]); + assert_eq!( + expected_error_disable_invalid_cheatcode.err().unwrap(), + disable_cheatcode_fails_on_invalid_cheatcode.err().unwrap(), + "Resulting error does not match the expected error" + ); + + let disable_all_cheatcodes_fails_with_invalid_config = rpc_server.disable_cheatcode( + Some(runloop_context.clone()), + CheatcodeFilter::All("not_all".to_string()), + None, + ); + let expected_error_disable_all_with_invalid_config: jsonrpc_core::Result<()> = + Err(SurfpoolError::disable_cheatcode( + "Invalid optioin provided for disabling all cheatcodes. Try using \"all\"".to_string(), + ) + .into()); assert!( - invalid_cheatcode_disable_result.is_err(), - "Expected passing an invalid cheatcode to be disabled to result in Error" + disable_all_cheatcodes_fails_with_invalid_config.is_err(), + "Expected surfnet_disableCheatcode to fail when config is wrong" + ); + assert_eq!( + disable_all_cheatcodes_fails_with_invalid_config + .err() + .unwrap(), + expected_error_disable_all_with_invalid_config + .err() + .unwrap(), + "Expected error did not match the resulting error" ); - // cheatcode_config.lockout tests - // disabling enableCheatcode and disableCheatcode should result in error when lockout == false - let disable_cheatcode_enable_rpc_method_result = rpc_server.disable_cheatcode( + let disabled_all_cheatcode_passes_result = rpc_server.disable_cheatcode( Some(runloop_context.clone()), - vec![RpcCheatcodes::EnableCheatcode.into()], + CheatcodeFilter::All("all".to_string()), + None, ); - let disable_cheatcode_disable_rpc_method_result = rpc_server.disable_cheatcode( + + assert!( + disabled_all_cheatcode_passes_result.is_ok(), + "Expected surfnet_disableCheatcode to pass with valid config" + ); + + assert_eq!( + runloop_context.cheatcode_config.lock().unwrap().filter, + CheatcodeConfig::filter_all_list(false), + "The disabled cheatcodes list doesn't match the expected one" + ); + + let disable_fails_if_cheatcode_already_disabled_result = rpc_server.disable_cheatcode( Some(runloop_context.clone()), - vec![RpcCheatcodes::DisableCheatcode.into()], + CheatcodeFilter::List(vec![valid_cheatcode_method.clone()]), + None, ); + + let expected_error_if_cheatcode_already_disabled: jsonrpc_core::Result<()> = + Err(SurfpoolError::disable_cheatcode("Cheatcode already disabled".to_string()).into()); + assert!( - disable_cheatcode_enable_rpc_method_result.is_err() - && disable_cheatcode_disable_rpc_method_result.is_err(), - "Expected disable_cheatcode to fail when trying to disable surfnet_enableCheatcode rpc method when lockout == false" + disable_fails_if_cheatcode_already_disabled_result.is_err(), + "Expected surfnet_disableCheatcode to fail if cheatcode already disabled" ); - let enable_lockout_result = rpc_server.lockout(Some(runloop_context.clone())); + assert_eq!( + disable_fails_if_cheatcode_already_disabled_result + .err() + .unwrap(), + expected_error_if_cheatcode_already_disabled.err().unwrap(), + "The expected error does not match the resulting error" + ); + // test enable works properly + let enable_cheatcode_works_properly_result = rpc_server.enable_cheatcode( + Some(runloop_context.clone()), + CheatcodeFilter::List(vec![valid_cheatcode_method.clone()]), + ); + + assert!( + enable_cheatcode_works_properly_result.is_ok(), + "expected surfnet_enableCheatcode to pass" + ); assert!( - enable_lockout_result.is_ok(), - "Expected lockout rpc method to succeed" + !runloop_context + .cheatcode_config + .lock() + .unwrap() + .is_cheatcode_disabled(&valid_cheatcode_method), + "Expected the cheatcode to be enabled after surnet_enableCheatcode rpc method" + ); + + let enable_cheatcode_fails_on_invalid_cheatcode = rpc_server.enable_cheatcode( + Some(runloop_context.clone()), + CheatcodeFilter::List(vec![invalid_cheatcode_method]), ); + let expected_error_enable_invalid_cheatcode: jsonrpc_core::Result<()> = + Err(SurfpoolError::enable_cheatcode("Invalid cheatcode rpc method".to_string()).into()); + assert!( - runloop_context.cheatcode_config.lock().unwrap().lockout, - "Expected lockout to be true" + enable_cheatcode_fails_on_invalid_cheatcode.is_err(), + "Expected surfnet_disableCheatcode to fail on providing invalid cheatcode method" ); - let disable_cheatcode_control_rpc_method_succeed_result = rpc_server.disable_cheatcode( + assert_eq!( + enable_cheatcode_fails_on_invalid_cheatcode.err().unwrap(), + expected_error_enable_invalid_cheatcode.err().unwrap(), + "Resulting error does not match the expected error" + ); + + let enable_all_cheatcodes_fails_with_invalid_config = rpc_server.enable_cheatcode( Some(runloop_context.clone()), - vec![RpcCheatcodes::EnableCheatcode.into()], + CheatcodeFilter::All("not_all".to_string()), ); + let expected_error_enable_all_with_invalid_config: jsonrpc_core::Result<()> = + Err(SurfpoolError::enable_cheatcode( + "Invalid optioin provided for enabling all cheatcodes. Try using \"all\"".to_string(), + ) + .into()); assert!( - disable_cheatcode_control_rpc_method_succeed_result.is_ok(), - "Expected disable_cheatcode to pass when trying to disable surfnet_enableCheatcode rpc method when lockout == true" + enable_all_cheatcodes_fails_with_invalid_config.is_err(), + "Expected surfnet_disableCheatcode to fail when config is wrong" + ); + assert_eq!( + enable_all_cheatcodes_fails_with_invalid_config + .err() + .unwrap(), + expected_error_enable_all_with_invalid_config.err().unwrap(), + "Expected error did not match the resulting error" ); - // Disable all cheatcode tests - // NOTE: Testing whether the cheatcode rpc method are disabled after the call the disable_all_cheatcodes is not possible as - // filtering happens in the on_request middleware - let disable_all_result = rpc_server.disable_all_cheatcodes(Some(runloop_context.clone())); + let enable_all_cheatcodes_pass_result = rpc_server.enable_cheatcode( + Some(runloop_context.clone()), + CheatcodeFilter::All("all".to_string()), + ); assert!( - disable_all_result.is_ok(), - "Expected disable all rpc method to work without fail" + enable_all_cheatcodes_pass_result.is_ok(), + "Expected surnet_enableCheatcode with correct config to pass" + ); + + assert_eq!( + runloop_context.cheatcode_config.lock().unwrap().filter, + CheatcodeFilter::List(vec![]), + "Expected all the cheatcodes to be enabled" + ); + + let disable_all_with_lockout_passes = rpc_server.disable_cheatcode( + Some(runloop_context.clone()), + CheatcodeFilter::All("all".to_string()), + Some(CheatcodeControlConfig { + lockout: Some(true), + }), ); assert!( - runloop_context - .cheatcode_config - .lock() - .unwrap() - .filter - .eq(&surfpool_types::CheatcodeFilter::All), - "Expected cheatcode filter to have the CheatcodeFilter::All variant" + disable_all_with_lockout_passes.is_ok(), + "Expected surfnet_disableCheatcode with lockout == true to pass" ); + + assert_eq!( + runloop_context.cheatcode_config.lock().unwrap().filter, + CheatcodeConfig::filter_all_list(true), + "Expected all the features to be disabled" + ); + + // assert that surfnet_enableCheatcode and surfnet_disableCheatcode both are disabled so that on_request Middleware sucessfully filters them + for ref cheatcode in [enable_cheatcode_method, disable_cheatcode_method] { + assert!( + runloop_context + .cheatcode_config + .lock() + .unwrap() + .is_cheatcode_disabled(cheatcode), + "Expected {} to be disabled", + cheatcode + ); + } } #[test_case(TestType::sqlite(); "with on-disk sqlite db")] From f68e806b63317116c677ce6a3a8ef6e34bc341c7 Mon Sep 17 00:00:00 2001 From: 0xzrf Date: Wed, 11 Mar 2026 14:19:38 +0530 Subject: [PATCH 4/8] Added proper functionality and tests for enable/disable cheatcode --- crates/types/src/types.rs | 52 +++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs index 857315a5..940a74ef 100644 --- a/crates/types/src/types.rs +++ b/crates/types/src/types.rs @@ -1258,18 +1258,22 @@ impl RunbookExecutionStatusReport { } } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CheatcodeConfig { pub lockout: bool, // if true, allows disabling even the `surfnet_enableCheatcodes`/`surfnetdisableCheatcodes` methods pub filter: CheatcodeFilter, } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Default)] +pub struct CheatcodeControlConfig { + pub lockout: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum CheatcodeFilter { - #[default] - All, + All(String), List(Vec), // disables cheatcodes in a named list } @@ -1285,8 +1289,8 @@ impl CheatcodeConfig { self.lockout = true; } - pub fn disable_all(&mut self) { - self.filter = CheatcodeFilter::All; + pub fn disable_all(&mut self, lockout: bool) { + self.filter = Self::filter_all_list(lockout); } pub fn disable_cheatcode(&mut self, cheatcode: &String) -> Result<(), String> { @@ -1324,7 +1328,41 @@ impl CheatcodeConfig { pub fn is_cheatcode_disabled(&self, cheatcode: &String) -> bool { match &self.filter { CheatcodeFilter::List(list) => list.contains(cheatcode), - CheatcodeFilter::All => true, + CheatcodeFilter::All(_) => true, + } + } + + pub fn filter_all_list(lockout: bool) -> CheatcodeFilter { + // when lockout == true, it's important to disable surfnet_disableCheatcode as well + // since calling surfnet_disableCheatcode with lockout == false will override the current config, which is a bug + if lockout { + CheatcodeFilter::All("all".to_string()) + } else { + let filter = vec![ + RpcCheatcodes::SetAccount.into(), + RpcCheatcodes::SetTokenAccount.into(), + RpcCheatcodes::CloneProgramAccount.into(), + RpcCheatcodes::ProfileTransaction.into(), + RpcCheatcodes::GetProfileResultsByTag.into(), + RpcCheatcodes::SetSupply.into(), + RpcCheatcodes::SetProgramAuthority.into(), + RpcCheatcodes::GetTransactionProfile.into(), + RpcCheatcodes::RegisterIdl.into(), + RpcCheatcodes::GetActiveIdl.into(), + RpcCheatcodes::GetLocalSignatures.into(), + RpcCheatcodes::TimeTravel.into(), + RpcCheatcodes::PauseClock.into(), + RpcCheatcodes::ResumeClock.into(), + RpcCheatcodes::ResetAccount.into(), + RpcCheatcodes::ResetNetwork.into(), + RpcCheatcodes::ExportSnapshot.into(), + RpcCheatcodes::StreamAccount.into(), + RpcCheatcodes::GetStreamedAccounts.into(), + RpcCheatcodes::GetSurfnetInfo.into(), + RpcCheatcodes::WriteProgram.into(), + RpcCheatcodes::RegisterScenario.into(), + ]; + CheatcodeFilter::List(filter) } } } From d1843a7956155b05270b0c35ef2037b8153daf82 Mon Sep 17 00:00:00 2001 From: Micaiah Reid Date: Thu, 12 Mar 2026 14:56:54 -0400 Subject: [PATCH 5/8] fix: automatically generate list of available cheatcodes --- crates/core/src/rpc/mod.rs | 7 +- crates/core/src/rpc/surfnet_cheatcodes.rs | 36 ++++-- crates/core/src/runloops/mod.rs | 18 ++- crates/types/src/types.rs | 127 ++-------------------- 4 files changed, 57 insertions(+), 131 deletions(-) diff --git a/crates/core/src/rpc/mod.rs b/crates/core/src/rpc/mod.rs index 5ee55bbd..36d6f236 100644 --- a/crates/core/src/rpc/mod.rs +++ b/crates/core/src/rpc/mod.rs @@ -12,9 +12,7 @@ use jsonrpc_core::{ }; use jsonrpc_pubsub::{PubSubMetadata, Session}; use solana_clock::Slot; -use surfpool_types::{ - CheatcodeConfig, RpcCheatcodes, SimnetCommand, SimnetEvent, types::RpcConfig, -}; +use surfpool_types::{CheatcodeConfig, SimnetCommand, SimnetEvent, types::RpcConfig}; use crate::{ PluginManagerCommand, @@ -182,7 +180,8 @@ impl Middleware> for SurfpoolMiddleware { cheatcode_config: self.cheatcode_config.clone(), }); - if RpcCheatcodes::try_from(method_name.as_str()).is_ok() + // All surfnet cheatcodes will start with surfnet. If the request is a cheatcode, make sure it isn't disabled. + if method_name.starts_with("surfnet_") && let Some(meta_val) = meta.clone() && meta_val .cheatcode_config diff --git a/crates/core/src/rpc/surfnet_cheatcodes.rs b/crates/core/src/rpc/surfnet_cheatcodes.rs index bb9475c0..d634215b 100644 --- a/crates/core/src/rpc/surfnet_cheatcodes.rs +++ b/crates/core/src/rpc/surfnet_cheatcodes.rs @@ -1,4 +1,7 @@ -use std::collections::BTreeMap; +use std::{ + collections::BTreeMap, + sync::{Arc, RwLock}, +}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use jsonrpc_core::{BoxFuture, Error, Result, futures::future}; @@ -15,7 +18,7 @@ use solana_transaction::versioned::VersionedTransaction; use spl_associated_token_account_interface::address::get_associated_token_address_with_program_id; use surfpool_types::{ AccountSnapshot, CheatcodeControlConfig, CheatcodeFilter, ClockCommand, ExportSnapshotConfig, - GetStreamedAccountsResponse, GetSurfnetInfoResponse, Idl, ResetAccountConfig, RpcCheatcodes, + GetStreamedAccountsResponse, GetSurfnetInfoResponse, Idl, ResetAccountConfig, RpcProfileResultConfig, Scenario, SimnetCommand, SimnetEvent, StreamAccountConfig, UiKeyedProfileResult, types::{AccountUpdate, SetSomeAccount, SupplyUpdate, TokenAccountUpdate, UuidOrSignature}, @@ -1218,7 +1221,23 @@ pub trait SurfnetCheatcodes { } #[derive(Clone)] -pub struct SurfnetCheatcodesRpc; +pub struct SurfnetCheatcodesRpc { + pub registered_methods: Arc>>, +} +impl SurfnetCheatcodesRpc { + pub fn empty() -> Self { + Self { + registered_methods: Arc::new(RwLock::new(vec![])), + } + } + pub fn is_available_cheatcode(&self, cheatcode: &String) -> bool { + let Ok(methods) = self.registered_methods.read() else { + return false; + }; + methods.contains(cheatcode) + } +} + impl SurfnetCheatcodes for SurfnetCheatcodesRpc { type Metadata = Option; @@ -1314,18 +1333,21 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { .into()); } - cheatcode_ctx.disable_all(lockout); + let Ok(available_cheatcodes) = self.registered_methods.read() else { + return Err(jsonrpc_core::Error::internal_error()); + }; + cheatcode_ctx.disable_all(lockout, (*available_cheatcodes).clone()); } CheatcodeFilter::List(cheatdcodes) => { for cheatcode in cheatdcodes { - if !lockout && cheatcode.eq(&String::from(RpcCheatcodes::EnableCheatcode)) { + if !lockout && cheatcode.eq("surfnet_enableCheatcode") { return Err(SurfpoolError::disable_cheatcode( "Cannot disable surfnet_enableCheatcode rpc method when lockout is not enabledd".to_string(), ) .into()); } debug!("disabling cheatcode: {cheatcode}"); - if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { + if !self.is_available_cheatcode(&cheatcode) { return Err(SurfpoolError::disable_cheatcode( "Invalid cheatcode rpc method".to_string(), ) @@ -1372,7 +1394,7 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { CheatcodeFilter::List(cheatcodes) => { for ref cheatcode in cheatcodes { debug!("enabling cheatcode: {cheatcode}"); - if RpcCheatcodes::try_from(cheatcode.as_str()).is_err() { + if !self.is_available_cheatcode(&cheatcode) { return Err(SurfpoolError::enable_cheatcode( "Invalid cheatcode rpc method".to_string(), ) diff --git a/crates/core/src/runloops/mod.rs b/crates/core/src/runloops/mod.rs index ee689bdf..4e5c006a 100644 --- a/crates/core/src/runloops/mod.rs +++ b/crates/core/src/runloops/mod.rs @@ -1053,12 +1053,28 @@ async fn start_http_rpc_server_runloop( .map_err(|e| e.to_string())?; let mut io = MetaIoHandler::with_middleware(middleware); + + // Cheatcodes should be added first and are a special case. One of the cheatcode methods needs access + // to the list of all cheatcode methods. The IoHandler allows us to iterate over them, so we're + // initializing and storing that list here. + { + let cheatcode_methods: Arc>> = Arc::new(RwLock::new(vec![])); + let mut cheatcodes_impl = rpc::surfnet_cheatcodes::SurfnetCheatcodesRpc { + registered_methods: Arc::clone(&cheatcode_methods), + }; + io.extend_with(cheatcodes_impl.to_delegate()); + + cheatcode_methods + .write() + .unwrap() + .extend(io.iter().map(|(n, _)| n.clone()).collect::>()); + } + io.extend_with(rpc::minimal::SurfpoolMinimalRpc.to_delegate()); io.extend_with(rpc::full::SurfpoolFullRpc.to_delegate()); io.extend_with(rpc::accounts_data::SurfpoolAccountsDataRpc.to_delegate()); io.extend_with(rpc::accounts_scan::SurfpoolAccountsScanRpc.to_delegate()); io.extend_with(rpc::bank_data::SurfpoolBankDataRpc.to_delegate()); - io.extend_with(rpc::surfnet_cheatcodes::SurfnetCheatcodesRpc.to_delegate()); io.extend_with(rpc::admin::SurfpoolAdminRpc.to_delegate()); if !config.plugin_config_path.is_empty() { diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs index 940a74ef..17f30acf 100644 --- a/crates/types/src/types.rs +++ b/crates/types/src/types.rs @@ -1289,8 +1289,8 @@ impl CheatcodeConfig { self.lockout = true; } - pub fn disable_all(&mut self, lockout: bool) { - self.filter = Self::filter_all_list(lockout); + pub fn disable_all(&mut self, lockout: bool, available_cheatcodes: Vec) { + self.filter = Self::filter_all_list(lockout, available_cheatcodes); } pub fn disable_cheatcode(&mut self, cheatcode: &String) -> Result<(), String> { @@ -1332,133 +1332,22 @@ impl CheatcodeConfig { } } - pub fn filter_all_list(lockout: bool) -> CheatcodeFilter { + pub fn filter_all_list(lockout: bool, available_cheatcodes: Vec) -> CheatcodeFilter { // when lockout == true, it's important to disable surfnet_disableCheatcode as well // since calling surfnet_disableCheatcode with lockout == false will override the current config, which is a bug if lockout { CheatcodeFilter::All("all".to_string()) } else { - let filter = vec![ - RpcCheatcodes::SetAccount.into(), - RpcCheatcodes::SetTokenAccount.into(), - RpcCheatcodes::CloneProgramAccount.into(), - RpcCheatcodes::ProfileTransaction.into(), - RpcCheatcodes::GetProfileResultsByTag.into(), - RpcCheatcodes::SetSupply.into(), - RpcCheatcodes::SetProgramAuthority.into(), - RpcCheatcodes::GetTransactionProfile.into(), - RpcCheatcodes::RegisterIdl.into(), - RpcCheatcodes::GetActiveIdl.into(), - RpcCheatcodes::GetLocalSignatures.into(), - RpcCheatcodes::TimeTravel.into(), - RpcCheatcodes::PauseClock.into(), - RpcCheatcodes::ResumeClock.into(), - RpcCheatcodes::ResetAccount.into(), - RpcCheatcodes::ResetNetwork.into(), - RpcCheatcodes::ExportSnapshot.into(), - RpcCheatcodes::StreamAccount.into(), - RpcCheatcodes::GetStreamedAccounts.into(), - RpcCheatcodes::GetSurfnetInfo.into(), - RpcCheatcodes::WriteProgram.into(), - RpcCheatcodes::RegisterScenario.into(), - ]; + // remove `surfnet_disableCheatcode` and `surfnet_enableCheatcode` from the list of available cheatcodes + let filter = available_cheatcodes + .into_iter() + .filter(|c| (c.ne("surfnet_disableCheatcode") && c.ne("surfnet_enableCheatcode"))) + .collect(); CheatcodeFilter::List(filter) } } } -pub enum RpcCheatcodes { - SetAccount, - EnableCheatcode, - DisableCheatcode, - SetTokenAccount, - CloneProgramAccount, - ProfileTransaction, - GetProfileResultsByTag, - SetSupply, - SetProgramAuthority, - GetTransactionProfile, - RegisterIdl, - GetActiveIdl, - GetLocalSignatures, - TimeTravel, - PauseClock, - ResumeClock, - ResetAccount, - ResetNetwork, - ExportSnapshot, - StreamAccount, - GetStreamedAccounts, - GetSurfnetInfo, - WriteProgram, - RegisterScenario, -} - -impl From for String { - fn from(value: RpcCheatcodes) -> Self { - match value { - RpcCheatcodes::SetAccount => String::from("surfnet_setAccount"), - RpcCheatcodes::EnableCheatcode => String::from("surfnet_enableCheatcode"), - RpcCheatcodes::DisableCheatcode => String::from("surfnet_disableCheatcode"), - RpcCheatcodes::SetTokenAccount => String::from("surfnet_setTokenAccount"), - RpcCheatcodes::CloneProgramAccount => String::from("surfnet_cloneProgramAccount"), - RpcCheatcodes::ProfileTransaction => String::from("surfnet_profileTransaction"), - RpcCheatcodes::GetProfileResultsByTag => String::from("surfnet_getProfileResultsByTag"), - RpcCheatcodes::SetSupply => String::from("surfnet_setSupply"), - RpcCheatcodes::SetProgramAuthority => String::from("surfnet_setProgramAuthority"), - RpcCheatcodes::GetTransactionProfile => String::from("surfnet_getTransactionProfile"), - RpcCheatcodes::RegisterIdl => String::from("surfnet_registerIdl"), - RpcCheatcodes::GetActiveIdl => String::from("surfnet_getActiveIdl"), - RpcCheatcodes::GetLocalSignatures => String::from("surfnet_getLocalSignatures"), - RpcCheatcodes::TimeTravel => String::from("surfnet_timeTravel"), - RpcCheatcodes::PauseClock => String::from("surfnet_pauseClock"), - RpcCheatcodes::ResumeClock => String::from("surfnet_resumeClock"), - RpcCheatcodes::ResetAccount => String::from("surfnet_resetAccount"), - RpcCheatcodes::ResetNetwork => String::from("surfnet_resetNetwork"), - RpcCheatcodes::ExportSnapshot => String::from("surfnet_exportSnapshot"), - RpcCheatcodes::StreamAccount => String::from("surfnet_streamAccount"), - RpcCheatcodes::GetStreamedAccounts => String::from("surfnet_getStreamedAccounts"), - RpcCheatcodes::GetSurfnetInfo => String::from("surfnet_getSurfnetInfo"), - RpcCheatcodes::WriteProgram => String::from("surfnet_writeProgram"), - RpcCheatcodes::RegisterScenario => String::from("surfnet_registerScenario"), - } - } -} - -impl TryFrom<&str> for RpcCheatcodes { - type Error = String; - - fn try_from(value: &str) -> Result { - match value { - "surfnet_setAccount" => Ok(RpcCheatcodes::SetAccount), - "surfnet_enableCheatcode" => Ok(RpcCheatcodes::EnableCheatcode), - "surfnet_disableCheatcode" => Ok(RpcCheatcodes::DisableCheatcode), - "surfnet_setTokenAccount" => Ok(RpcCheatcodes::SetTokenAccount), - "surfnet_cloneProgramAccount" => Ok(RpcCheatcodes::CloneProgramAccount), - "surfnet_profileTransaction" => Ok(RpcCheatcodes::ProfileTransaction), - "surfnet_getProfileResultsByTag" => Ok(RpcCheatcodes::GetProfileResultsByTag), - "surfnet_setSupply" => Ok(RpcCheatcodes::SetSupply), - "surfnet_setProgramAuthority" => Ok(RpcCheatcodes::SetProgramAuthority), - "surfnet_getTransactionProfile" => Ok(RpcCheatcodes::GetTransactionProfile), - "surfnet_registerIdl" => Ok(RpcCheatcodes::RegisterIdl), - "surfnet_getActiveIdl" => Ok(RpcCheatcodes::GetActiveIdl), - "surfnet_getLocalSignatures" => Ok(RpcCheatcodes::GetLocalSignatures), - "surfnet_timeTravel" => Ok(RpcCheatcodes::TimeTravel), - "surfnet_pauseClock" => Ok(RpcCheatcodes::PauseClock), - "surfnet_resumeClock" => Ok(RpcCheatcodes::ResumeClock), - "surfnet_resetAccount" => Ok(RpcCheatcodes::ResetAccount), - "surfnet_resetNetwork" => Ok(RpcCheatcodes::ResetNetwork), - "surfnet_exportSnapshot" => Ok(RpcCheatcodes::ExportSnapshot), - "surfnet_streamAccount" => Ok(RpcCheatcodes::StreamAccount), - "surfnet_getStreamedAccounts" => Ok(RpcCheatcodes::GetStreamedAccounts), - "surfnet_getSurfnetInfo" => Ok(RpcCheatcodes::GetSurfnetInfo), - "surfnet_writeProgram" => Ok(RpcCheatcodes::WriteProgram), - "surfnet_registerScenario" => Ok(RpcCheatcodes::RegisterScenario), - _ => Err(format!("unknown cheatcode method: {}", value)), - } - } -} - #[cfg(test)] mod tests { use serde_json::json; From 737f70bf4233a1931d3e996501ba440cc2d66605 Mon Sep 17 00:00:00 2001 From: Micaiah Reid Date: Thu, 12 Mar 2026 15:20:58 -0400 Subject: [PATCH 6/8] fix: update tests for new enable/disable cheatcodes changes --- crates/core/src/rpc/accounts_scan.rs | 12 ++--- crates/core/src/rpc/surfnet_cheatcodes.rs | 44 +++++++++--------- crates/core/src/tests/integration.rs | 55 +++++++++++++---------- fix/get-signatures-ordering | 1 + 4 files changed, 60 insertions(+), 52 deletions(-) create mode 160000 fix/get-signatures-ordering diff --git a/crates/core/src/rpc/accounts_scan.rs b/crates/core/src/rpc/accounts_scan.rs index 07761e11..d2241489 100644 --- a/crates/core/src/rpc/accounts_scan.rs +++ b/crates/core/src/rpc/accounts_scan.rs @@ -910,7 +910,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_set_and_get_supply() { let setup = TestSetup::new(SurfpoolAccountsScanRpc); - let cheatcodes_rpc = SurfnetCheatcodesRpc; + let cheatcodes_rpc = SurfnetCheatcodesRpc::empty(); // test initial default values let initial_supply = setup @@ -959,7 +959,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_get_supply_exclude_accounts() { let setup = TestSetup::new(SurfpoolAccountsScanRpc); - let cheatcodes_rpc = SurfnetCheatcodesRpc; + let cheatcodes_rpc = SurfnetCheatcodesRpc::empty(); // set supply with non-circulating accounts let supply_update = SupplyUpdate { @@ -1007,7 +1007,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_partial_supply_update() { let setup = TestSetup::new(SurfpoolAccountsScanRpc); - let cheatcodes_rpc = SurfnetCheatcodesRpc; + let cheatcodes_rpc = SurfnetCheatcodesRpc::empty(); // set initial values let initial_update = SupplyUpdate { @@ -1051,7 +1051,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_set_supply_with_multiple_invalid_pubkeys() { let setup = TestSetup::new(SurfpoolAccountsScanRpc); - let cheatcodes_rpc = SurfnetCheatcodesRpc; + let cheatcodes_rpc = SurfnetCheatcodesRpc::empty(); let invalid_pubkey = "invalid_pubkey"; // test with multiple invalid pubkeys - should fail on the first one @@ -1082,7 +1082,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_set_supply_with_max_values() { let setup = TestSetup::new(SurfpoolAccountsScanRpc); - let cheatcodes_rpc = SurfnetCheatcodesRpc; + let cheatcodes_rpc = SurfnetCheatcodesRpc::empty(); let supply_update = SupplyUpdate { total: Some(u64::MAX), @@ -1112,7 +1112,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_set_supply_large_valid_account_list() { let setup = TestSetup::new(SurfpoolAccountsScanRpc); - let cheatcodes_rpc = SurfnetCheatcodesRpc; + let cheatcodes_rpc = SurfnetCheatcodesRpc::empty(); let large_account_list: Vec = (0..100) .map(|i| match i % 10 { diff --git a/crates/core/src/rpc/surfnet_cheatcodes.rs b/crates/core/src/rpc/surfnet_cheatcodes.rs index d634215b..3a494d4e 100644 --- a/crates/core/src/rpc/surfnet_cheatcodes.rs +++ b/crates/core/src/rpc/surfnet_cheatcodes.rs @@ -2092,7 +2092,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_get_transaction_profile() { // Create connection to local validator - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let recent_blockhash = client .context .svm_locker @@ -2925,7 +2925,7 @@ mod tests { #[test] fn test_export_snapshot() { - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let pubkey1 = Pubkey::new_unique(); let account1 = Account { @@ -2961,7 +2961,7 @@ mod tests { #[test] fn test_export_snapshot_json_parsed() { - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let pubkey1 = Pubkey::new_unique(); println!("Pubkey1: {}", pubkey1); @@ -3057,7 +3057,7 @@ mod tests { use solana_signature::Signature; use surfpool_types::{ProfileResult, types::KeyedProfileResult}; - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); // Create several accounts in the network let account1_pubkey = Pubkey::new_unique(); @@ -3202,7 +3202,7 @@ mod tests { included_program_account_pubkey ); - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let system_account = Account { lamports: 1_000_000, @@ -3297,7 +3297,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_creates_accounts_automatically() { // Test that both program and program data accounts are created if they don't exist - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); // Verify accounts don't exist initially @@ -3375,7 +3375,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_single_chunk_small() { // Test writing a small program in a single write - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let program_data = vec![0x01, 0x02, 0x03, 0x04, 0x05]; @@ -3424,7 +3424,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_single_chunk_large() { // Test writing a large program (1MB) in a single write - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let program_data = vec![0xAB; 1024 * 1024]; // 1 MB @@ -3473,7 +3473,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_multiple_sequential_chunks() { // Test writing a program in multiple sequential chunks - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let chunks = vec![ @@ -3538,7 +3538,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_non_sequential_chunks() { // Test writing chunks out of order (backwards) - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let chunks = vec![ @@ -3604,7 +3604,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_overlapping_writes() { // Test that overlapping writes correctly overwrite previous data - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); // Write initial data @@ -3672,7 +3672,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_zero_offset() { // Test writing at offset 0 (should write immediately after metadata) - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let data = vec![0x42; 128]; @@ -3710,7 +3710,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_large_offset() { // Test writing at a large offset (account should expand) - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let large_offset = 1024 * 1024; // 1 MB offset @@ -3759,7 +3759,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_empty_data() { // Test writing empty data (should succeed but not write anything) - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let result = client @@ -3781,7 +3781,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_single_byte() { // Test writing a single byte - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let data = vec![0x42]; @@ -3819,7 +3819,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_invalid_program_id() { // Test with invalid program ID - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let result = client .rpc @@ -3844,7 +3844,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_invalid_hex_data() { // Test with invalid hex encoding - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let invalid_hex_strings = vec![ @@ -3883,7 +3883,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_rent_exemption() { // Test that rent exemption is maintained when account expands - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); // Write initial small data @@ -3966,7 +3966,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_account_ownership() { // Test that created accounts have correct ownership - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let authority = Keypair::new(); @@ -4046,7 +4046,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_metadata_preservation() { // Test that program data account metadata is preserved across writes - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); // First write @@ -4113,7 +4113,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_idempotent() { // Test that writing the same data twice produces the same result - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let data = vec![0x55; 512]; @@ -4179,7 +4179,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn test_write_program_context_slot() { // Test that response context contains valid slot - let client = TestSetup::new(SurfnetCheatcodesRpc); + let client = TestSetup::new(SurfnetCheatcodesRpc::empty()); let program_id = Keypair::new(); let result = client diff --git a/crates/core/src/tests/integration.rs b/crates/core/src/tests/integration.rs index e21a7fe4..c6e770d0 100644 --- a/crates/core/src/tests/integration.rs +++ b/crates/core/src/tests/integration.rs @@ -33,8 +33,8 @@ use solana_system_interface::{ use solana_transaction::{Transaction, versioned::VersionedTransaction}; use surfpool_types::{ CheatcodeConfig, CheatcodeControlConfig, CheatcodeFilter, DEFAULT_SLOT_TIME_MS, Idl, - RpcCheatcodes, RpcProfileDepth, RpcProfileResultConfig, SimnetCommand, SimnetEvent, - SurfpoolConfig, UiAccountChange, UiAccountProfileState, UiKeyedProfileResult, + RpcProfileDepth, RpcProfileResultConfig, SimnetCommand, SimnetEvent, SurfpoolConfig, + UiAccountChange, UiAccountProfileState, UiKeyedProfileResult, types::{ BlockProductionMode, RpcConfig, SimnetConfig, SubgraphConfig, TransactionStatusEvent, UuidOrSignature, @@ -764,7 +764,7 @@ async fn test_simulate_transaction_no_signers(test_type: TestType) { #[tokio::test(flavor = "multi_thread")] async fn test_surfnet_estimate_compute_units(test_type: TestType) { let (mut svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); - let rpc_server = crate::rpc::surfnet_cheatcodes::SurfnetCheatcodesRpc; + let rpc_server = crate::rpc::surfnet_cheatcodes::SurfnetCheatcodesRpc::empty(); let payer = Keypair::new(); let recipient = Pubkey::new_unique(); @@ -1055,15 +1055,22 @@ async fn test_surfnet_estimate_compute_units(test_type: TestType) { #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_enable_and_disable_cheatcodes(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let valid_cheatcode_method = "surfnet_getActiveIdl".to_string(); + let enable_cheatcode_method = "surfnet_enableCheatcode".to_string(); + let disable_cheatcode_method = "surfnet_disableCheatcode".to_string(); + let invalid_cheatcode_method = "surfnet_invalidCheatcode".to_string(); + let available_methods = vec![ + valid_cheatcode_method.clone(), + enable_cheatcode_method.clone(), + disable_cheatcode_method.clone(), + ]; + let rpc_server = SurfnetCheatcodesRpc { + registered_methods: Arc::new(std::sync::RwLock::new(available_methods.clone())), + }; let (svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance); let (simnet_cmd_tx, _simnet_cmd_rx) = crossbeam_unbounded::(); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); - let valid_cheatcode_method: String = RpcCheatcodes::GetActiveIdl.into(); - let enable_cheatcode_method: String = RpcCheatcodes::EnableCheatcode.into(); - let disable_cheatcode_method: String = RpcCheatcodes::DisableCheatcode.into(); - let invalid_cheatcode_method = "surfnet_invalidCheatcode".to_string(); let runloop_context = RunloopContext { id: None, @@ -1176,7 +1183,7 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { assert_eq!( runloop_context.cheatcode_config.lock().unwrap().filter, - CheatcodeConfig::filter_all_list(false), + CheatcodeConfig::filter_all_list(false, available_methods.clone()), "The disabled cheatcodes list doesn't match the expected one" ); @@ -1293,7 +1300,7 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { assert_eq!( runloop_context.cheatcode_config.lock().unwrap().filter, - CheatcodeConfig::filter_all_list(true), + CheatcodeConfig::filter_all_list(true, vec![]), "Expected all the features to be disabled" ); @@ -1317,7 +1324,7 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] #[tokio::test(flavor = "multi_thread")] async fn test_get_transaction_profile(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (mut svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); // Set up test accounts @@ -1539,7 +1546,7 @@ async fn test_get_transaction_profile(test_type: TestType) { #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_register_and_get_idl_without_slot(test_type: TestType) { let idl: Idl = serde_json::from_slice(include_bytes!("./assets/idl_v1.json")).unwrap(); - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance); @@ -1595,7 +1602,7 @@ fn test_register_and_get_idl_without_slot(test_type: TestType) { #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_register_and_get_idl_with_slot(test_type: TestType) { let idl: Idl = serde_json::from_slice(include_bytes!("./assets/idl_v1.json")).unwrap(); - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance); @@ -1664,7 +1671,7 @@ async fn test_register_and_get_same_idl_with_different_slots(test_type: TestType let idl_v1: Idl = serde_json::from_slice(include_bytes!("./assets/idl_v1.json")).unwrap(); let idl_v2: Idl = serde_json::from_slice(include_bytes!("./assets/idl_v2.json")).unwrap(); let idl_v3: Idl = serde_json::from_slice(include_bytes!("./assets/idl_v3.json")).unwrap(); - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance); @@ -3357,7 +3364,7 @@ async fn test_profile_transaction_versioned_message(test_type: TestType) { #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] #[tokio::test(flavor = "multi_thread")] async fn test_get_local_signatures_without_limit(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance.clone()); @@ -3463,7 +3470,7 @@ async fn test_get_local_signatures_without_limit(test_type: TestType) { #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] #[tokio::test(flavor = "multi_thread")] async fn test_get_local_signatures_with_limit(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); let svm_locker_for_context = SurfnetSvmLocker::new(svm_instance.clone()); @@ -3673,7 +3680,7 @@ fn boot_simnet( #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_time_travel_resume_paused_clock(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_locker, simnet_cmd_tx, _) = boot_simnet(BlockProductionMode::Clock, Some(100), test_type); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); @@ -3751,7 +3758,7 @@ fn test_time_travel_resume_paused_clock(test_type: TestType) { #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_time_travel_absolute_timestamp(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let slot_time = 100; let (svm_locker, simnet_cmd_tx, simnet_events_rx) = boot_simnet( BlockProductionMode::Clock, @@ -3838,7 +3845,7 @@ fn test_time_travel_absolute_timestamp(test_type: TestType) { #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_time_travel_absolute_slot(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_locker, simnet_cmd_tx, simnet_events_rx) = boot_simnet(BlockProductionMode::Clock, Some(400), test_type); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); @@ -3915,7 +3922,7 @@ fn test_time_travel_absolute_slot(test_type: TestType) { #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_time_travel_absolute_epoch(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_locker, simnet_cmd_tx, simnet_events_rx) = boot_simnet(BlockProductionMode::Clock, Some(400), test_type); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); @@ -4649,7 +4656,7 @@ fn test_reset_network_keeps_latest_blockhash_valid(test_type: TestType) { #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_reset_network_time_travel_timestamp(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_locker, simnet_cmd_tx, simnet_events_rx) = boot_simnet(BlockProductionMode::Clock, Some(400), test_type); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); @@ -4702,7 +4709,7 @@ fn test_reset_network_time_travel_timestamp(test_type: TestType) { #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_reset_network_time_travel_slot(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_locker, simnet_cmd_tx, simnet_events_rx) = boot_simnet(BlockProductionMode::Clock, Some(400), test_type); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); @@ -4759,7 +4766,7 @@ fn test_reset_network_time_travel_slot(test_type: TestType) { #[test_case(TestType::no_db(); "with no db")] #[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))] fn test_reset_network_time_travel_epoch(test_type: TestType) { - let rpc_server = SurfnetCheatcodesRpc; + let rpc_server = SurfnetCheatcodesRpc::empty(); let (svm_locker, simnet_cmd_tx, simnet_events_rx) = boot_simnet(BlockProductionMode::Clock, Some(400), test_type); let (plugin_cmd_tx, _plugin_cmd_rx) = crossbeam_unbounded::(); @@ -7215,7 +7222,7 @@ fn test_nonce_accounts() { #[tokio::test(flavor = "multi_thread")] async fn test_profile_transaction_does_not_mutate_state(test_type: TestType) { let (mut svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm(); - let rpc_server = crate::rpc::surfnet_cheatcodes::SurfnetCheatcodesRpc; + let rpc_server = crate::rpc::surfnet_cheatcodes::SurfnetCheatcodesRpc::empty(); // Setup: Create accounts and fund the payer let payer = Keypair::new(); diff --git a/fix/get-signatures-ordering b/fix/get-signatures-ordering new file mode 160000 index 00000000..31683fa7 --- /dev/null +++ b/fix/get-signatures-ordering @@ -0,0 +1 @@ +Subproject commit 31683fa71976efaff45a052fd2bb6191a68b2994 From 1419d28522ab7839da4c0ff8e98b7e7574fb6938 Mon Sep 17 00:00:00 2001 From: 0xzrf Date: Fri, 13 Mar 2026 09:47:11 +0530 Subject: [PATCH 7/8] added Result handling from Mutex::lock() --- crates/core/src/rpc/mod.rs | 44 ++++++++++++++--------- crates/core/src/rpc/surfnet_cheatcodes.rs | 20 ++++++----- crates/core/src/tests/integration.rs | 7 ++-- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/crates/core/src/rpc/mod.rs b/crates/core/src/rpc/mod.rs index 36d6f236..04dbcfc5 100644 --- a/crates/core/src/rpc/mod.rs +++ b/crates/core/src/rpc/mod.rs @@ -183,23 +183,35 @@ impl Middleware> for SurfpoolMiddleware { // All surfnet cheatcodes will start with surfnet. If the request is a cheatcode, make sure it isn't disabled. if method_name.starts_with("surfnet_") && let Some(meta_val) = meta.clone() - && meta_val - .cheatcode_config - .lock() - .unwrap() // this is okay since only on_request, disable_cheatcode and enable_cheatcode only use it, the rpc method being called after on_request - .is_cheatcode_disabled(&method_name) { - let error = Response::from( - Error { - code: ErrorCode::InvalidRequest, - message: format!("Cheatsheet rpc method: {method_name} is currently disabled"), - data: None, - }, - None, - ); - warn!("Request rejected due to cheatsheet being disabled"); - - return Either::Left(Box::pin(async move { Some(error) })); + let Ok(meta_val) = meta_val.cheatcode_config.lock() else { + let error = Response::from( + Error { + code: ErrorCode::InternalError, + message: "An internal server error occured".to_string(), + data: None, + }, + None, + ); + warn!("Request rejected due to cheatsheet being disabled"); + + return Either::Left(Box::pin(async move { Some(error) })); + }; + if meta_val.is_cheatcode_disabled(&method_name) { + let error = Response::from( + Error { + code: ErrorCode::InvalidRequest, + message: format!( + "Cheatcode rpc method: {method_name} is currently disabled" + ), + data: None, + }, + None, + ); + warn!("Request rejected due to cheatcode rpc method being disabled"); + + return Either::Left(Box::pin(async move { Some(error) })); + } } Either::Left(Box::pin(next(request, meta).map(move |res| { diff --git a/crates/core/src/rpc/surfnet_cheatcodes.rs b/crates/core/src/rpc/surfnet_cheatcodes.rs index 3a494d4e..4bd97c21 100644 --- a/crates/core/src/rpc/surfnet_cheatcodes.rs +++ b/crates/core/src/rpc/surfnet_cheatcodes.rs @@ -1322,13 +1322,15 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { let lockout = lockout.unwrap_or_default(); if let Some(runloop_ctx) = meta { - let mut cheatcode_ctx = runloop_ctx.cheatcode_config.lock().unwrap(); + let Ok(mut cheatcode_ctx) = runloop_ctx.cheatcode_config.lock() else { + return Err(jsonrpc_core::Error::internal_error()); + }; match cheatcodes_filter { CheatcodeFilter::All(all) => { if all.ne("all") { return Err(SurfpoolError::disable_cheatcode( - "Invalid optioin provided for disabling all cheatcodes. Try using \"all\"".to_string(), + "Invalid option provided for disabling all cheatcodes. Try using 'all' or providing an array of specific cheatcodes".to_string(), ) .into()); } @@ -1342,7 +1344,7 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { for cheatcode in cheatdcodes { if !lockout && cheatcode.eq("surfnet_enableCheatcode") { return Err(SurfpoolError::disable_cheatcode( - "Cannot disable surfnet_enableCheatcode rpc method when lockout is not enabledd".to_string(), + "Cannot disable surfnet_enableCheatcode rpc method when lockout is not enabled".to_string(), ) .into()); } @@ -1378,30 +1380,32 @@ impl SurfnetCheatcodes for SurfnetCheatcodesRpc { Err(e) => return Err(e.into()), }; if let Some(runloop_ctx) = meta { - let cheatcode_ctx = runloop_ctx.cheatcode_config; + let Ok(mut cheatcode_ctx) = runloop_ctx.cheatcode_config.lock() else { + return Err(jsonrpc_core::Error::internal_error()); + }; match cheatcodes_filter { CheatcodeFilter::All(all) => { if all.ne("all") { return Err(SurfpoolError::enable_cheatcode( - "Invalid optioin provided for enabling all cheatcodes. Try using \"all\"".to_string(), + "Invalid option provided for enabling all cheatcodes. Try using 'all' or providing an array of specific cheatcodes".to_string(), ) .into()); } // we probably don't need to check whether lockout == true because surfnet_enableCheatcode won't be called if it's disabled - cheatcode_ctx.lock().unwrap().filter = CheatcodeFilter::List(vec![]); + cheatcode_ctx.filter = CheatcodeFilter::List(vec![]); } CheatcodeFilter::List(cheatcodes) => { for ref cheatcode in cheatcodes { debug!("enabling cheatcode: {cheatcode}"); - if !self.is_available_cheatcode(&cheatcode) { + if !self.is_available_cheatcode(cheatcode) { return Err(SurfpoolError::enable_cheatcode( "Invalid cheatcode rpc method".to_string(), ) .into()); } - if let Err(e) = cheatcode_ctx.lock().unwrap().enable_cheatcode(cheatcode) { + if let Err(e) = cheatcode_ctx.enable_cheatcode(cheatcode) { return Err(SurfpoolError::enable_cheatcode(e).into()); } } diff --git a/crates/core/src/tests/integration.rs b/crates/core/src/tests/integration.rs index c6e770d0..bf60b205 100644 --- a/crates/core/src/tests/integration.rs +++ b/crates/core/src/tests/integration.rs @@ -1110,8 +1110,7 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { ); let expected_error: jsonrpc_core::Result<()> = Err(SurfpoolError::disable_cheatcode( - "Cannot disable surfnet_enableCheatcode rpc method when lockout is not enabledd" - .to_string(), + "Cannot disable surfnet_enableCheatcode rpc method when lockout is not enabled".to_string(), ) .into()); @@ -1152,7 +1151,7 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { ); let expected_error_disable_all_with_invalid_config: jsonrpc_core::Result<()> = Err(SurfpoolError::disable_cheatcode( - "Invalid optioin provided for disabling all cheatcodes. Try using \"all\"".to_string(), + "Invalid option provided for disabling all cheatcodes. Try using 'all' or providing an array of specific cheatcodes".to_string(), ) .into()); @@ -1253,7 +1252,7 @@ fn test_enable_and_disable_cheatcodes(test_type: TestType) { ); let expected_error_enable_all_with_invalid_config: jsonrpc_core::Result<()> = Err(SurfpoolError::enable_cheatcode( - "Invalid optioin provided for enabling all cheatcodes. Try using \"all\"".to_string(), + "Invalid option provided for enabling all cheatcodes. Try using 'all' or providing an array of specific cheatcodes".to_string(), ) .into()); From be1443c986c0e9addac8148830c8516f0724e83a Mon Sep 17 00:00:00 2001 From: 0xzrf Date: Fri, 13 Mar 2026 09:47:29 +0530 Subject: [PATCH 8/8] added types --- crates/types/src/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs index 17f30acf..1e311181 100644 --- a/crates/types/src/types.rs +++ b/crates/types/src/types.rs @@ -1296,9 +1296,9 @@ impl CheatcodeConfig { pub fn disable_cheatcode(&mut self, cheatcode: &String) -> Result<(), String> { if !self.lockout && (cheatcode.eq("surfpool_enableCheatcode") - || cheatcode.eq("surfpool_enableCheatcode")) + || cheatcode.eq("surfpool_disableCheatcode")) { - return Err("Cannot disable surfpool_enableCheatcode and surfpool_enableCheatcode while lockout is is false".to_string()); + return Err("Cannot disable surfpool_disableCheatcode or surfpool_enableCheatcode while lockout is is false".to_string()); } if let CheatcodeFilter::List(list) = &mut self.filter {