Skip to content

Refactor configuration management to support JSON format#292

Merged
nahimterrazas merged 9 commits intomainfrom
285-tomls-all-over-refactor-these-to-dedicated-types
Mar 10, 2026
Merged

Refactor configuration management to support JSON format#292
nahimterrazas merged 9 commits intomainfrom
285-tomls-all-over-refactor-these-to-dedicated-types

Conversation

@nahimterrazas
Copy link
Collaborator

@nahimterrazas nahimterrazas commented Feb 10, 2026

  • Removed TOML configuration files and replaced them with JSON equivalents.
  • Updated the configuration loading logic to parse JSON instead of TOML.
  • Adjusted account implementations to handle JSON-based configurations.
  • Enhanced validation and error handling for JSON configurations.
  • Updated README and CLI commands to reflect the new JSON configuration format.
  • Added a new demo configuration file in JSON format.

Summary

Testing Process

Checklist

  • Add a reference to related issues in the PR description.
  • Add unit tests if applicable.

Summary by CodeRabbit

  • Configuration

    • System config format migrated from TOML to JSON; added consolidated config/demo.json and support for loading .json runtime configs (including from storage).
  • New Features

    • CLI: added "init load-storage" to load a solver’s runtime config from storage (optional solver-id) and materialize it locally.
  • Documentation

    • Admin API and quick-start updated to include JSON seed overrides and storage-based runtime-load guidance.
  • Scripts

    • E2E/setup and demo init scripts now produce and consume JSON configs.

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Workspace-wide migration from TOML to JSON: configuration types and factory signatures moved from toml::Value to serde_json::Value, TOML multi-file loader removed, demo/test configs switched to JSON, and demo CLI gained an init load-storage path that materializes runtime JSON configs from storage.

Changes

Cohort / File(s) Summary
Workspace Manifests
Cargo.toml, crates/*/Cargo.toml
Removed workspace toml deps; added/propagated serde_json where required.
Top-level & Demo configs
config/demo.json, config/demo.toml, config/demo/*
Added config/demo.json; deleted config/demo.toml and per-section TOML fragments (api.toml, gas.toml, networks.toml).
Docs & README
README.md, crates/solver-demo/README.md
Docs updated to reference JSON configs, seed override JSON, and new init load-storage/init load-storage --solver-id flows.
Config core & loader
crates/solver-config/src/lib.rs, crates/solver-config/src/loader.rs
Config parsing switched to serde_json; the TOML multi-file include-aware loader was removed. Config structs now hold serde_json::Value.
Factory/type surface
crates/*/src/lib.rs, crates/solver-service/src/factory_registry.rs, crates/solver-core/src/builder/mod.rs
All public factory type aliases and builder generics now accept &serde_json::Value instead of &toml::Value.
Account implementations
crates/solver-account/src/..., crates/solver-account/Cargo.toml
Local and KMS account factories reworked to parse/validate JSON configs; signatures and tests updated.
Delivery / Discovery / Order / Strategy
crates/solver-delivery/..., crates/solver-discovery/..., crates/solver-order/...
Schemas, factories, and tests migrated to JSON values (as_tableas_object, as_integeras_i64).
Pricing
crates/solver-pricing/src/...
Coingecko, DefiLlama, Mock pricing implementations accept JSON configs; parsing and tests updated.
Settlement & utils
crates/solver-settlement/src/...
Settlement schemas, parsing, and helpers migrated to JSON-based parsing and signatures.
Storage
crates/solver-storage/src/...
Storage factories/backends (file, memory, redis) accept JSON configs; TTL parsing and async initializers updated.
Validation/types
crates/solver-types/src/validation.rs
Validation API migrated to serde_json::Value; added json_type_name and adapted validators/errors to JSON types.
Service, merge & builders
crates/solver-service/src/config_merge.rs, crates/solver-service/src/*, crates/solver-service/src/server.rs
Config merge/builders rewritten to produce/consume JSON (toml_tablejson_object); many tests updated.
Demo CLI & init
crates/solver-demo/src/bin/solver-demo.rs, .../cli/commands/init.rs, .../operations/init/mod.rs, .../core/config.rs
Added init load-storage subcommand and load-from-storage pipeline that materializes runtime JSON config; demo generation changed to JSON.
Session/env helpers
crates/solver-demo/src/core/session.rs, .../operations/env/mod.rs
Session section mapping and address injection adapted to JSON (inject_address_to_json).
E2E scripts
scripts/e2e/setup_testnet.sh, scripts/e2e/batch_intents.sh
Scripts now produce/consume JSON configs (config/testnet.json, networks.json, api.json, gas.json); added settlement/order finalization helpers.
Tests
crates/*/tests and modules
Widespread test updates: toml fixtures replaced with serde_json::Value and json! constructors.
Removed loader
crates/solver-config/src/loader.rs
Deleted TOML multi-file include-aware loader and its tests.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as "oif-demo CLI"
    participant InitOps as "InitOps::load_from_storage"
    participant ConfigStore as "OperatorConfig Store"
    participant Storage as "Storage backend (Redis/File/Memory)"
    participant FS as "Filesystem (.oif-demo/config)"

    CLI->>InitOps: load_from_storage(solver_id?, is_local)
    InitOps->>ConfigStore: build store key & request latest OperatorConfig
    ConfigStore->>Storage: fetch(operator_config_key)
    Storage-->>ConfigStore: return OperatorConfig (JSON)
    ConfigStore-->>InitOps: return OperatorConfig JSON
    InitOps->>FS: materialize_runtime_config(JSON) -> write generated JSON path
    InitOps-->>CLI: return path to generated JSON config
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • NicoMolinaOZ
  • shahnami
  • pepebndc

Poem

"I’m a rabbit with a JSON grin,
I hopped through TOML and pulled it thin.
I fetched configs from storage deep,
Wrote them out so builds can sleep.
🐇✨"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers the main changes but lacks critical details. Testing Process section is empty, and checklist items (related issues and unit tests) are incomplete and unchecked. Complete the Testing Process section with actual testing details and check/complete the checklist items (add issue references and confirm unit tests were added).
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: converting configuration management from TOML to JSON format, which is the primary objective across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 285-tomls-all-over-refactor-these-to-dedicated-types

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
crates/solver-discovery/src/lib.rs (1)

50-52: ⚠️ Potential issue | 🟡 Minor

Stale doc comment still references TOML.

Line 51 says "The schema is used to validate TOML configuration" but the factory now accepts serde_json::Value.

Suggested fix
-	/// with specific validation rules. The schema is used to validate TOML configuration
-	/// before initializing the discovery implementation.
+	/// with specific validation rules. The schema is used to validate JSON configuration
+	/// before initializing the discovery implementation.
crates/solver-demo/src/core/session.rs (1)

79-79: ⚠️ Potential issue | 🟡 Minor

Stale comment references TOML.

Line 79: "Always create a fresh session to ensure it reflects the current TOML configuration" should say JSON.

Suggested fix
-		// Always create a fresh session to ensure it reflects the current TOML configuration
+		// Always create a fresh session to ensure it reflects the current JSON configuration
crates/solver-delivery/src/lib.rs (1)

102-104: ⚠️ Potential issue | 🟡 Minor

Stale documentation references TOML.

Line 103 still says "validate TOML configuration" but the factory now accepts serde_json::Value.

📝 Suggested fix
-	/// with specific validation rules. The schema is used to validate TOML configuration
+	/// with specific validation rules. The schema is used to validate JSON configuration
crates/solver-types/src/validation.rs (1)

54-59: ⚠️ Potential issue | 🟡 Minor

Docs still mention TOML in the validator section.

Update this docstring to refer to JSON values to match the new API.

✏️ Suggested doc update
-/// beyond type checking. They receive a TOML value and return an error
+/// beyond type checking. They receive a JSON value and return an error
crates/solver-delivery/src/implementations/evm/alloy.rs (1)

568-579: ⚠️ Potential issue | 🟡 Minor

Doc comment still says TOML.

Please update the comment to reflect JSON configuration.

✏️ Suggested doc update
-/// - `config`: TOML configuration containing:
+/// - `config`: JSON configuration containing:
crates/solver-order/src/lib.rs (1)

69-74: ⚠️ Potential issue | 🟡 Minor

Refresh config_schema docs to reference JSON.

Factory signatures now take JSON values, but the surrounding doc comments still say TOML, which can mislead implementers.

✏️ Suggested doc update
-	/// with specific validation rules. The schema is used to validate TOML configuration
+	/// with specific validation rules. The schema is used to validate JSON configuration
-	/// with specific validation rules. The schema is used to validate TOML configuration
+	/// with specific validation rules. The schema is used to validate JSON configuration

Also applies to: 148-153, 162-177

crates/solver-settlement/src/lib.rs (1)

163-168: ⚠️ Potential issue | 🟡 Minor

Update config_schema docs to JSON wording.

The trait docs still reference TOML despite the JSON-based factory signature.

✏️ Suggested doc update
-	/// with specific validation rules. The schema is used to validate TOML configuration
+	/// with specific validation rules. The schema is used to validate JSON configuration

Also applies to: 237-248

crates/solver-account/src/lib.rs (1)

51-56: ⚠️ Potential issue | 🟡 Minor

Update config_schema docs to JSON wording.

The trait docs still mention TOML even though the factory now consumes JSON values.

✏️ Suggested doc update
-	/// with specific validation rules. The schema is used to validate TOML configuration
+	/// with specific validation rules. The schema is used to validate JSON configuration

Also applies to: 100-106

crates/solver-core/src/builder/mod.rs (1)

36-41: ⚠️ Potential issue | 🟡 Minor

Update SolverFactories docs to JSON wording.

The struct docs still reference TOML even though factories now accept JSON values.

✏️ Suggested doc update
-/// service type required by the solver engine. Each factory function takes
-/// a TOML configuration value and returns the corresponding service implementation.
+/// service type required by the solver engine. Each factory function takes
+/// a JSON configuration value and returns the corresponding service implementation.

Also applies to: 80-109

crates/solver-settlement/src/utils.rs (1)

20-67: ⚠️ Potential issue | 🟡 Minor

Silent success when table is not a JSON object.

parse_oracle_table (and similarly parse_routes_table at line 84) returns Ok(empty HashMap) when the input is not a JSON object (e.g., a string, array, or null). This silently accepts malformed config. Consider returning a validation error when as_object() yields None.

Proposed fix
 pub fn parse_oracle_table(
 	table: &serde_json::Value,
 ) -> Result<HashMap<u64, Vec<Address>>, SettlementError> {
 	let mut result = HashMap::new();
 
-	if let Some(table) = table.as_object() {
+	let table = table.as_object().ok_or_else(|| {
+		SettlementError::ValidationFailed("Expected a JSON object for oracle table".to_string())
+	})?;
+	{
 		for (chain_id_str, oracles_value) in table {
crates/solver-order/src/implementations/standards/_7683.rs (1)

796-818: ⚠️ Potential issue | 🟡 Minor

Stale doc comment: still references "TOML configuration value".

Line 803 says config - TOML configuration value (may be empty)but the parameter is now&serde_json::Value`.

Proposed fix
-/// * `config` - TOML configuration value (may be empty)
+/// * `config` - JSON configuration value (may be empty)
crates/solver-pricing/src/implementations/defillama.rs (1)

478-484: ⚠️ Potential issue | 🟡 Minor

Error messages say "table" instead of "object" for JSON context.

Lines 482 and 512 report expected: "table" in ValidationError::TypeMismatch, but the JSON equivalent is "object". This is a minor inconsistency leftover from the TOML migration.

Proposed fix
-				expected: "table".to_string(),
+				expected: "object".to_string(),

Also applies to: 508-514

crates/solver-storage/src/implementations/file.rs (1)

1246-1272: ⚠️ Potential issue | 🟡 Minor

Test function name is stale: test_ttl_config_from_toml.

The test now uses JSON config but the function name still references TOML.

-	async fn test_ttl_config_from_toml() {
+	async fn test_ttl_config_from_json() {
crates/solver-config/src/lib.rs (1)

668-673: ⚠️ Potential issue | 🟡 Minor

Negative JSON integer for network_id would wrap to a large u64.

If a config file contains a negative value in network_ids (e.g., [-1]), as_i64() returns Some(-1) and the as u64 cast wraps to u64::MAX. This would then fail the networks.contains_key check on line 684 with a confusing error message like "references network 18446744073709551615 which doesn't exist." Consider adding an explicit check for non-negative values.

Proposed fix
-				let network_id = network_value.as_i64().ok_or_else(|| {
+				let network_id_i64 = network_value.as_i64().ok_or_else(|| {
 					ConfigError::Validation(format!(
 						"Invalid network_id in settlement '{impl_name}'"
 					))
-				})? as u64;
+				})?;
+				if network_id_i64 < 0 {
+					return Err(ConfigError::Validation(format!(
+						"Negative network_id {network_id_i64} in settlement '{impl_name}'"
+					)));
+				}
+				let network_id = network_id_i64 as u64;
🤖 Fix all issues with AI agents
In `@crates/solver-demo/src/operations/init/mod.rs`:
- Around line 320-322: The chain ID comparison uses as_i64() causing incorrect
None for values > i64::MAX; update the closure in the
network_ids.iter().any(...) (the has_chain computation that references
network_ids and chain_id) to use as_u64() and compare directly to chain_id
(remove the i as u64 cast). Make the identical change for the other occurrence
mentioned (the similar network_ids check around the second occurrence near line
361) so both comparisons use as_u64() against chain_id.

In `@crates/solver-pricing/src/implementations/coingecko.rs`:
- Around line 87-95: The parsed `cache_duration_seconds` and
`rate_limit_delay_ms` values can be negative and wrap when cast to u64; update
the parsing chains that compute `cache_duration` and `rate_limit_delay_ms` to
filter out negative integers (e.g., add a `.filter(|v| *v >= 0)` step before
`unwrap_or` so only non‑negative values are accepted) and update the
configuration/schema validation logic that currently checks type (the validation
block referenced in the comment) to explicitly reject negative values with a
clear error message like "value must be non-negative" so invalid negative inputs
fail fast.

In `@crates/solver-pricing/src/lib.rs`:
- Around line 124-125: Update the stale doc comment on the function from_table
to accurately describe the input type: change "TOML table" to reflect that it
accepts a serde_json::Value (e.g., a JSON object/value such as a strategy
implementation table), so the doc matches the function signature pub fn
from_table(table: &serde_json::Value) -> Self and clearly documents expected
JSON structure.

In `@crates/solver-storage/src/implementations/file.rs`:
- Around line 136-137: Doc comment for the function from_config incorrectly
mentions "TOML"; update the comment to reflect the actual input type
(serde_json::Value/JSON) by replacing "TOML" with "JSON" or "serde_json::Value"
in the doc string for from_config so the documentation matches the function
signature and behavior.

In `@crates/solver-storage/src/implementations/redis.rs`:
- Around line 129-139: The doc comment on the from_config function incorrectly
references TOML; update it to say JSON to match the serde_json::Value parameter
and new config format. Locate the from_config function (and related mentions
around StorageKey::all and the config parameter) and replace any "TOML" wording
in the function-level documentation and comments with "JSON" (or "JSON
configuration") so the docs reflect the current code and parameter types.

In `@scripts/e2e/setup_testnet.sh`:
- Around line 212-436: The min_profitability_pct is being passed to jq with
--arg (making it a JSON string) but should be emitted as a JSON number; update
the jq argument for min_profitability_pct from --arg min_profitability_pct
"$min_profitability_pct" to use --argjson so the produced config field
solver.min_profitability_pct is numeric (reference the min_profitability_pct
shell variable and the jq invocation that builds the JSON, and ensure the
resulting field in the generated config/testnet.json remains unchanged except
for number type).
🧹 Nitpick comments (11)
crates/solver-demo/src/operations/init/mod.rs (2)

313-388: Extract shared oracle lookup to reduce duplication.

get_input_oracle_for_chain and get_output_oracle_for_chain are identical except for the "input" / "output" key. Consider a single helper:

Suggested refactor
+fn get_oracle_for_chain(
+	settlement_config: &SettlementConfig,
+	chain_id: u64,
+	direction: &str, // "input" or "output"
+) -> Option<String> {
+	settlement_config.implementations.values().find_map(|impl_config| {
+		let network_ids = impl_config.get("network_ids")?.as_array()?;
+		let has_chain = network_ids.iter().any(|id| id.as_u64() == Some(chain_id));
+		if !has_chain {
+			return None;
+		}
+		let oracles = impl_config.get("oracles")?.as_object()?;
+		let direction_table = oracles.get(direction)?.as_object()?;
+		let arr = direction_table.get(&chain_id.to_string())?.as_array()?;
+		arr.first()?.as_str().map(String::from)
+	})
+}
+
 fn get_input_oracle_for_chain(
 	settlement_config: &SettlementConfig,
 	chain_id: u64,
 ) -> Option<String> {
-	// ... 35 lines of nested ifs ...
+	get_oracle_for_chain(settlement_config, chain_id, "input")
 }
 
 fn get_output_oracle_for_chain(
 	settlement_config: &SettlementConfig,
 	chain_id: u64,
 ) -> Option<String> {
-	// ... 35 lines of nested ifs ...
+	get_oracle_for_chain(settlement_config, chain_id, "output")
 }

This also flattens the deeply nested if let chains (6+ levels) into idiomatic ?-chaining.


391-394: Remove unused _config_name parameter.

The parameter is unused (underscore-prefixed). Removing it from both the signature and the call site (line 190) simplifies the API.

crates/solver-service/src/apis/quote/mod.rs (1)

363-371: Consider extracting a shared test helper for solver engine construction.

The pattern of creating a SolverEngine with empty JSON configs for strategy/pricing is duplicated across at least three test files (order.rs, validators/order.rs, quote/mod.rs). A shared test utility would reduce boilerplate and keep the migration consistent.

crates/solver-service/src/apis/quote/validation.rs (1)

723-765: Significant test config boilerplate duplication across files.

This exact JSON config block is repeated in server.rs::build_test_solver_engine, tokens.rs::create_mock_solver_engine, tokens.rs::create_test_config, and here. Consider extracting a shared test_utils::default_test_config() helper to reduce maintenance burden and drift risk.

crates/solver-service/src/config_merge.rs (1)

959-973: Consider consolidating identical JSON clone helpers.

json_identity and json_clone both just clone the value; keeping a single helper (or inlining .clone()) would reduce surface area.

Also applies to: 1070-1073, 1569-1572

crates/solver-settlement/src/utils.rs (1)

90-98: Negative JSON integers silently become large u64 chain IDs.

v.as_i64().map(|i| i as u64) will wrap negative values (e.g., -1u64::MAX - 1). While chain IDs should always be positive, a validation guard would catch misconfigured JSON early.

Proposed fix
-					v.as_i64().map(|i| i as u64).ok_or_else(|| {
+					v.as_u64().ok_or_else(|| {
 						SettlementError::ValidationFailed(format!(
 							"Destination chain ID must be integer for route from chain {chain_id}"
 						))
 					})

serde_json::Value::as_u64() exists and directly rejects negative numbers, making this both simpler and safer.

crates/solver-pricing/src/implementations/defillama.rs (1)

67-83: cache_duration_seconds could silently accept negative values.

v.as_i64().unwrap_or(60) as u64 at line 82–83 will wrap negative JSON integers into large u64 values. Consider using as_u64() or adding a non-negative check, since the validator at line 458 only checks that the value is parseable as i64, not that it's non-negative.

crates/solver-account/src/implementations/local.rs (1)

29-66: Good use of deny_unknown_fields and dedicated config struct.

The LocalWalletConfig with from_json + validate follows the same pattern as KmsWalletConfig, providing consistent config parsing across account implementations. The hex validation logic is correct.

One consideration: private_key is held as a plain String in LocalWalletConfig. While the struct is private and short-lived (only used during parsing), be mindful that serde error messages or debug formatting could inadvertently leak the key value. The #[derive(Debug)] on line 30 would include the private key in debug output.

Consider removing Debug derive or redacting the field
-#[derive(Debug, Deserialize)]
+#[derive(Deserialize)]
 #[serde(deny_unknown_fields)]
 struct LocalWalletConfig {
 	private_key: String,
 }

Alternatively, implement Debug manually to redact private_key.

crates/solver-storage/src/implementations/file.rs (1)

140-146: TTL parsing uses as_i64() then casts to u64.

The Schema validation (line 882) enforces min: Some(0) and create_storage calls validate_config before from_config, so negative values are rejected in the normal flow. However, if from_config is called independently without prior validation, negative values would silently wrap. Using as_u64() would be a defensive improvement.

crates/solver-settlement/src/implementations/direct.rs (1)

476-502: Test config construction is clean — consider a helper to reduce boilerplate.

The repeated pattern of building serde_json::Value::Object with nested maps across 7+ tests is verbose. A small helper function (similar to create_test_oracle_config) that builds a valid base JSON config would reduce duplication and improve maintainability.

crates/solver-settlement/src/implementations/hyperlane.rs (1)

1322-1350: parse_address_table silently returns empty map for non-object input.

If table is not a JSON object (e.g., it's a string or array), the function silently returns an empty HashMap instead of erroring. Since the caller already validated via schema that mailboxes and igp_addresses are tables, this is safe in practice, but a non-object value slipping through would be a silent misconfiguration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@crates/solver-demo/src/lib.rs`:
- Around line 244-273: The error strings in the InvalidConfig branches
(constructed in the function around SessionStore::load and config_path checks)
use inconsistent cargo invocation syntax; update all occurrences of "cargo run
-p solver-demo init ..." to include the "--" separator (i.e. "cargo run -p
solver-demo -- init ...") so messages are consistent—change the messages created
in the Error::InvalidConfig calls near Storage::new/session existence,
session_store.config_path() handling, empty config_path check, and the "Config
file not found" branch to use the "--" form.
🧹 Nitpick comments (5)
crates/solver-demo/src/core/config.rs (2)

62-62: Blocking I/O in async context.

std::fs::read_to_string blocks the async runtime's thread. The non-JSON path uses SolverFullConfig::from_file which internally uses tokio::fs::read_to_string. Use tokio::fs::read_to_string here as well for consistency.

♻️ Proposed fix
-			let content = std::fs::read_to_string(path).map_err(|e| {
+			let content = tokio::fs::read_to_string(path).await.map_err(|e| {

76-76: unwrap() on path.to_str() can panic on non-UTF-8 paths.

While unlikely in practice, this is inconsistent with the careful error handling elsewhere in this function.

♻️ Proposed fix
-			SolverFullConfig::from_file(path.to_str().unwrap()).await?
+			SolverFullConfig::from_file(
+				path.to_str()
+					.ok_or_else(|| Error::InvalidConfig("Config path is not valid UTF-8".into()))?
+			).await?
crates/solver-demo/src/operations/init/mod.rs (3)

419-436: Sanitization looks correct but consider a length cap.

The function handles special characters and the empty-result edge case well. For very long solver IDs, the generated filename could exceed filesystem limits (255 chars on most filesystems). Consider truncating to a safe length.


457-533: Deep nesting in oracle extraction functions — consider refactoring.

Both get_input_oracle_for_chain and get_output_oracle_for_chain have 8+ levels of nesting with near-identical structure. This is a good candidate for extraction into a shared helper, e.g.:

fn get_oracle_for_chain(
    settlement_config: &SettlementConfig,
    chain_id: u64,
    oracle_type: &str, // "input" or "output"
) -> Option<String> { ... }

This would eliminate the duplication and reduce cognitive complexity.


785-826: EnvVarGuard and CwdGuard will require unsafe blocks in Rust 2024 edition.

Since Rust 2024, std::env::set_var and std::env::remove_var are unsafe fn due to unsoundness in multi-threaded programs on non-Windows platforms. These calls currently compile but will fail in 2024 edition without wrapping in unsafe {} blocks. While the test isolation context mitigates the practical risk here, plan to update these to unsafe { std::env::set_var(...) } during 2024 edition migration, or consider using std::process::Command::env() if applicable to your use case.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/solver-config/src/lib.rs (1)

664-668: ⚠️ Potential issue | 🟠 Major

Use as_u64() to reject negative network IDs instead of silently wrapping them.

The current code uses as_i64() followed by as u64 cast. This allows negative values to silently wrap to huge numbers (e.g., -1 becomes 18446744073709551615), creating bogus keys and potentially bypassing network validation.

Suggested fix
-				let network_id = network_value.as_i64().ok_or_else(|| {
+				let network_id = network_value.as_u64().ok_or_else(|| {
 					ConfigError::Validation(format!(
-						"Invalid network_id in settlement '{impl_name}'"
+						"Invalid non-negative network_id in settlement '{impl_name}'"
 					))
-				})? as u64;
+				})?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/solver-config/src/lib.rs` around lines 664 - 668, The code currently
reads network_value.as_i64().ok_or_else(...) and then casts to u64, which allows
negative values to wrap; change this to use
network_value.as_u64().ok_or_else(...) so negatives are rejected outright and
remove the trailing "as u64" cast. Update the error path
(ConfigError::Validation with the same message) to trigger when as_u64() returns
None; locate the code that assigns network_id (the let network_id = ... block)
and make this single substitution to ensure proper validation.
♻️ Duplicate comments (1)
crates/solver-demo/src/operations/init/mod.rs (1)

467-467: ⚠️ Potential issue | 🟡 Minor

Use as_u64() for chain ID checks (same issue as previously flagged).

chain_id is u64; as_i64() will miss valid large IDs and introduces lossy cast semantics.

Suggested patch
-					.any(|id| id.as_i64().is_some_and(|i| i as u64 == chain_id));
+					.any(|id| id.as_u64().is_some_and(|i| i == chain_id));
...
-					.any(|id| id.as_i64().is_some_and(|i| i as u64 == chain_id));
+					.any(|id| id.as_u64().is_some_and(|i| i == chain_id));
#!/bin/bash
# Expect no matches after applying the fix.
rg -nP '\.as_i64\(\)\.is_some_and\(\|i\| i as u64 == chain_id\)' crates/solver-demo/src/operations/init/mod.rs

Also applies to: 506-506

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/solver-demo/src/operations/init/mod.rs` at line 467, The code checks
JSON numbers with .as_i64().is_some_and(|i| i as u64 == chain_id) which loses
large u64 values; change the check to use .as_u64() instead (e.g. .any(|id|
id.as_u64().is_some_and(|i| i == chain_id)) or .any(|id|
id.as_u64().map_or(false, |i| i == chain_id))) so the closure that uses chain_id
(the .any(|id| ... ) lambda) compares u64-to-u64 without casting.
🧹 Nitpick comments (3)
crates/solver-service/src/apis/tokens.rs (1)

342-400: Deduplicate repeated JSON config fixtures to reduce drift risk.

Lines 342-400, 732-810, and 814-875 duplicate a large shared config shape. This is brittle when schema fields evolve and increases maintenance cost.

♻️ Proposed refactor sketch
+fn base_test_config_json() -> serde_json::Value {
+    serde_json::json!({
+        "solver": {
+            "id": "test-solver",
+            "monitoring_timeout_seconds": 30,
+            "min_profitability_pct": 1.0
+        },
+        "storage": {
+            "primary": "memory",
+            "cleanup_interval_seconds": 3600,
+            "implementations": { "memory": {} }
+        },
+        "delivery": { "min_confirmations": 1, "implementations": {} },
+        "account": {
+            "primary": "local",
+            "implementations": {
+                "local": {
+                    "private_key": "0x1234567890123456789012345678901234567890123456789012345678901234"
+                }
+            }
+        },
+        "discovery": { "implementations": {} },
+        "order": {
+            "implementations": {},
+            "strategy": { "primary": "simple", "implementations": { "simple": {} } }
+        },
+        "settlement": { "implementations": {} }
+    })
+}
+
+fn config_from_networks(networks: serde_json::Value, err: &str) -> Config {
+    let mut v = base_test_config_json();
+    v["networks"] = networks;
+    serde_json::from_value(v).expect(err)
+}
+
 fn create_test_config() -> Config {
-    serde_json::from_value(serde_json::json!({ ...full duplicated object... }))
-        .expect("Failed to parse test config")
+    config_from_networks(
+        serde_json::json!({
+            "1": { ... },
+            "137": { ... }
+        }),
+        "Failed to parse test config",
+    )
 }
 
 fn create_test_config_with_empty_tokens() -> Config {
-    serde_json::from_value(serde_json::json!({ ...full duplicated object... }))
-        .expect("Failed to parse test config with empty tokens")
+    config_from_networks(
+        serde_json::json!({
+            "1": { ... "tokens": [] },
+            "137": { ... "tokens": [] }
+        }),
+        "Failed to parse test config with empty tokens",
+    )
 }

Also applies to: 732-810, 814-875

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/solver-service/src/apis/tokens.rs` around lines 342 - 400, The large
JSON test config used to build a solver_config::Config (the local variable
config in crates/solver-service/src/apis/tokens.rs) is duplicated across three
regions; extract it into a single reusable helper (e.g., fn make_test_config()
-> solver_config::Config or fn test_config_value() -> serde_json::Value) and
have each test call that helper and then .expect(...) as before. Replace the
inline serde_json::json!({ ... }) blocks with a call to the new helper, update
any callers to mutate only the few fields they need for specific cases, and keep
the helper in the same module or a shared test utils module so all occurrences
(the blocks around lines shown) reference one source of truth.
crates/solver-service/src/apis/quote/validation.rs (1)

723-765: Deduplicate the JSON test config fixture to avoid schema drift.

These two helpers duplicate a large config payload. A shared base fixture builder will reduce breakage when solver_config::Config evolves.

♻️ Proposed refactor
+fn base_test_config_json() -> serde_json::Value {
+    serde_json::json!({
+        "solver": {
+            "id": "test-solver",
+            "monitoring_timeout_seconds": 30,
+            "min_profitability_pct": 1.0
+        },
+        "storage": {
+            "primary": "memory",
+            "cleanup_interval_seconds": 3600,
+            "implementations": { "memory": {} }
+        },
+        "delivery": { "min_confirmations": 1, "implementations": {} },
+        "account": {
+            "primary": "local",
+            "implementations": {
+                "local": {
+                    "private_key": "0x1234567890123456789012345678901234567890123456789012345678901234"
+                }
+            }
+        },
+        "discovery": { "implementations": {} },
+        "order": {
+            "implementations": {},
+            "strategy": {
+                "primary": "simple",
+                "implementations": { "simple": {} }
+            }
+        },
+        "settlement": { "implementations": {} },
+        "networks": {}
+    })
+}
+
 async fn create_mock_solver_with_networks(networks: NetworksConfig) -> SolverEngine {
-    let config: Config = serde_json::from_value(serde_json::json!({ ... }))
+    let config: Config = serde_json::from_value(base_test_config_json())
         .expect("Failed to parse test config");
     ...
 }
 
 fn create_test_config_with_callbacks(
     simulate_callbacks: bool,
     whitelist: Vec<String>,
 ) -> solver_config::Config {
-    serde_json::from_value(serde_json::json!({ ... }))
+    let mut cfg = base_test_config_json();
+    cfg["order"]["simulate_callbacks"] = serde_json::json!(simulate_callbacks);
+    cfg["order"]["callback_whitelist"] = serde_json::json!(whitelist);
+    serde_json::from_value(cfg)
         .expect("Failed to parse test config")
 }

Also applies to: 772-774, 2097-2141

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/solver-service/src/apis/quote/validation.rs` around lines 723 - 765,
Multiple tests duplicate a large JSON fixture when constructing a Config (seen
as the local variable config: Config built via serde_json::from_value); extract
that JSON into a single helper (e.g., fn make_test_config() -> Config or fn
test_config_value(overrides: Option<Value>) -> Value) and have tests call it
instead of inlining the JSON blob. Implement the helper in this module (or a
shared test util) to return a parsed Config (using serde_json::from_value
internally) and add an optional parameter/map to apply small overrides for the
few tests that need differences; replace the duplicated inline serde_json::json!
blocks (including the other occurrences noted) with calls to the new helper so
schema changes to solver_config::Config are centralized.
crates/solver-demo/src/operations/env/mod.rs (1)

718-749: Consider using proper JSON parsing for more robust address injection.

The text-based string replacement approach works but has edge cases:

  1. No validation that resulting JSON remains valid after replacement
  2. replace() replaces all occurrences—unintended matches in comments or nested strings could be affected
  3. The unquoted pattern branch (line 724) adding quotes could break JSON structure in edge cases

For a local development CLI this is likely acceptable, but proper serde_json parsing would be more robust.

♻️ Suggested approach using serde_json
// Pseudocode approach:
let mut json: serde_json::Value = serde_json::from_str(&content)?;

// Recursively walk the JSON tree and replace matching string values
fn replace_in_value(value: &mut serde_json::Value, old: &str, new: &str) {
    match value {
        serde_json::Value::String(s) if s == old => *s = new.to_string(),
        serde_json::Value::Object(map) => {
            for v in map.values_mut() {
                replace_in_value(v, old, new);
            }
        }
        serde_json::Value::Array(arr) => {
            for v in arr {
                replace_in_value(v, old, new);
            }
        }
        _ => {}
    }
}

replace_in_value(&mut json, &placeholder_address, actual_address);
let updated_content = serde_json::to_string_pretty(&json)?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/solver-demo/src/operations/env/mod.rs` around lines 718 - 749, Replace
the fragile text-replace logic that builds patterns_to_replace and uses
updated_content.replace(...) with a proper serde_json-based approach: parse
content using serde_json::from_str into a serde_json::Value, implement a small
recursive function (e.g., replace_in_value(&mut serde_json::Value, old: &str,
new: &str)) that only replaces serde_json::Value::String values equal to
placeholder_address with actual_address, call that on the parsed JSON, then
serialize back with serde_json::to_string_pretty and write it to target_file;
keep the existing replacement_made/warning behavior by tracking whether
replace_in_value made any change and log the same message via logging::warning
if no replacement occurred, and return the same Error::Io/serde_json errors
mapped as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/solver-config/src/lib.rs`:
- Around line 712-715: The FromStr implementation for Config now uses
serde_json::from_str (via resolve_env_vars and Config::validate), so any test
fixture still providing TOML will fail; update the failing fixture(s) to supply
JSON instead of TOML (or call the JSON helper) so parse::<Config>() succeeds.
Locate uses of from_str / Config::from_str / config_str.parse::<Config>() and
replace the TOML-formatted fixture content with equivalent JSON that matches the
Config fields expected by serde_json::from_str (or alternatively change the
tests to parse via the JSON helper path).

In `@crates/solver-core/src/builder/mod.rs`:
- Around line 265-267: The code currently casts network_id_value.as_i64() to u64
which causes negative JSON numbers to wrap; change the handling in the block
that processes network_id_value so it uses network_id_value.as_u64() and
explicitly handles the None case (e.g., return an error or skip registration
with a clear diagnostic) instead of blind casting, updating the logic that
inserts into delivery_implementations to use the resulting u64 network_id; refer
to the network_id_value variable and the delivery_implementations insertion code
to make this replacement and add appropriate error handling.

In `@crates/solver-demo/src/operations/init/mod.rs`:
- Around line 412-417: The sanitized filename can collide for different solver
IDs because sanitize_solver_id_for_filename() can map distinct IDs to the same
safe name; update generated_runtime_config_path() to append a deterministic
short suffix derived from the original solver_id (e.g., first N hex chars of a
stable hash like SHA-256) so filenames become
format!("{safe_solver_id}-{hash}.json"); ensure the hash is computed from the
raw solver_id (not the sanitized one) and keep the suffix length small but
collision-resistant (e.g., 8 hex chars) and apply the same change consistently
wherever generated_runtime_config_path() is used.
- Around line 438-454: The file written by materialize_runtime_config uses
std::fs::write with default permissions and may leak secrets; change the
implementation to create and write the file with restrictive permissions (owner
read/write only) instead of std::fs::write: open the target via
std::fs::OpenOptions (referenced in materialize_runtime_config and output_path),
on Unix set the mode to 0o600 using std::os::unix::fs::OpenOptionsExt::mode
before writing, write the JSON bytes to that handle, and on non-Unix platforms
fall back to creating the file then immediately set permissions to read/write
for owner only (std::fs::set_permissions) or the platform-appropriate equivalent
to ensure the file is not world-readable. Ensure parent dir creation remains
unchanged and propagate errors as Result<()>.

In `@crates/solver-service/src/config_merge.rs`:
- Around line 1685-1693: The numeric extraction for Hyperlane fields
(default_gas_limit, message_timeout_seconds, the dests array filtering, and
dispute_period_seconds) currently uses as_i64() then casts to u64 which can wrap
negatives; replace those parses with a checked unsigned conversion using
v.as_u64().or_else(|| v.as_i64().and_then(|i| u64::try_from(i).ok())) so
negatives fail instead of wrapping, and propagate the fallback logic (unwrap_or
or ? handling) as before; update the parsing in the default_gas_limit and
message_timeout_seconds bindings, the dests filtering logic, and the
dispute_period_seconds binding to use this conversion helper inline.

---

Outside diff comments:
In `@crates/solver-config/src/lib.rs`:
- Around line 664-668: The code currently reads
network_value.as_i64().ok_or_else(...) and then casts to u64, which allows
negative values to wrap; change this to use
network_value.as_u64().ok_or_else(...) so negatives are rejected outright and
remove the trailing "as u64" cast. Update the error path
(ConfigError::Validation with the same message) to trigger when as_u64() returns
None; locate the code that assigns network_id (the let network_id = ... block)
and make this single substitution to ensure proper validation.

---

Duplicate comments:
In `@crates/solver-demo/src/operations/init/mod.rs`:
- Line 467: The code checks JSON numbers with .as_i64().is_some_and(|i| i as u64
== chain_id) which loses large u64 values; change the check to use .as_u64()
instead (e.g. .any(|id| id.as_u64().is_some_and(|i| i == chain_id)) or .any(|id|
id.as_u64().map_or(false, |i| i == chain_id))) so the closure that uses chain_id
(the .any(|id| ... ) lambda) compares u64-to-u64 without casting.

---

Nitpick comments:
In `@crates/solver-demo/src/operations/env/mod.rs`:
- Around line 718-749: Replace the fragile text-replace logic that builds
patterns_to_replace and uses updated_content.replace(...) with a proper
serde_json-based approach: parse content using serde_json::from_str into a
serde_json::Value, implement a small recursive function (e.g.,
replace_in_value(&mut serde_json::Value, old: &str, new: &str)) that only
replaces serde_json::Value::String values equal to placeholder_address with
actual_address, call that on the parsed JSON, then serialize back with
serde_json::to_string_pretty and write it to target_file; keep the existing
replacement_made/warning behavior by tracking whether replace_in_value made any
change and log the same message via logging::warning if no replacement occurred,
and return the same Error::Io/serde_json errors mapped as needed.

In `@crates/solver-service/src/apis/quote/validation.rs`:
- Around line 723-765: Multiple tests duplicate a large JSON fixture when
constructing a Config (seen as the local variable config: Config built via
serde_json::from_value); extract that JSON into a single helper (e.g., fn
make_test_config() -> Config or fn test_config_value(overrides: Option<Value>)
-> Value) and have tests call it instead of inlining the JSON blob. Implement
the helper in this module (or a shared test util) to return a parsed Config
(using serde_json::from_value internally) and add an optional parameter/map to
apply small overrides for the few tests that need differences; replace the
duplicated inline serde_json::json! blocks (including the other occurrences
noted) with calls to the new helper so schema changes to solver_config::Config
are centralized.

In `@crates/solver-service/src/apis/tokens.rs`:
- Around line 342-400: The large JSON test config used to build a
solver_config::Config (the local variable config in
crates/solver-service/src/apis/tokens.rs) is duplicated across three regions;
extract it into a single reusable helper (e.g., fn make_test_config() ->
solver_config::Config or fn test_config_value() -> serde_json::Value) and have
each test call that helper and then .expect(...) as before. Replace the inline
serde_json::json!({ ... }) blocks with a call to the new helper, update any
callers to mutate only the few fields they need for specific cases, and keep the
helper in the same module or a shared test utils module so all occurrences (the
blocks around lines shown) reference one source of truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 06c0b7aa-97eb-4642-9436-26b1e62f75f3

📥 Commits

Reviewing files that changed from the base of the PR and between 672fef5 and c2614f7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • README.md
  • crates/solver-config/src/lib.rs
  • crates/solver-core/src/builder/mod.rs
  • crates/solver-demo/src/operations/env/mod.rs
  • crates/solver-demo/src/operations/init/mod.rs
  • crates/solver-service/Cargo.toml
  • crates/solver-service/src/apis/order.rs
  • crates/solver-service/src/apis/quote/validation.rs
  • crates/solver-service/src/apis/tokens.rs
  • crates/solver-service/src/config_merge.rs
  • crates/solver-service/src/server.rs
  • crates/solver-service/src/validators/order.rs
💤 Files with no reviewable changes (1)
  • crates/solver-service/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/solver-service/src/validators/order.rs
  • README.md

@nahimterrazas nahimterrazas self-assigned this Mar 5, 2026
- Removed TOML configuration files and replaced them with JSON equivalents.
- Updated the configuration loading logic to parse JSON instead of TOML.
- Adjusted account implementations to handle JSON-based configurations.
- Enhanced validation and error handling for JSON configurations.
- Updated README and CLI commands to reflect the new JSON configuration format.
- Added a new demo configuration file in JSON format.
@nahimterrazas nahimterrazas force-pushed the 285-tomls-all-over-refactor-these-to-dedicated-types branch from 4aa48c3 to cd92906 Compare March 9, 2026 19:45
@nahimterrazas nahimterrazas merged commit 97e2ab4 into main Mar 10, 2026
8 checks passed
@nahimterrazas nahimterrazas deleted the 285-tomls-all-over-refactor-these-to-dedicated-types branch March 10, 2026 02:06
@coderabbitai coderabbitai bot mentioned this pull request Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants