From 5f01407f9a2da6ae97b01bfcf312ddc15b29684e Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:22:58 +0300 Subject: [PATCH 01/51] remove config source file --- crates/settings/src/config_source.rs | 925 --------------------------- crates/settings/src/lib.rs | 2 - 2 files changed, 927 deletions(-) delete mode 100644 crates/settings/src/config_source.rs diff --git a/crates/settings/src/config_source.rs b/crates/settings/src/config_source.rs deleted file mode 100644 index 522589734..000000000 --- a/crates/settings/src/config_source.rs +++ /dev/null @@ -1,925 +0,0 @@ -use crate::blocks::BlocksCfg; -use crate::remote::chains::{chainid::ChainIdError, RemoteNetworkError, RemoteNetworks}; -use crate::{GuiConfigSourceCfg, MetricCfg, PlotCfg}; -use alloy::primitives::{Address, U256}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use thiserror::Error; -use url::Url; -#[cfg(target_family = "wasm")] -use wasm_bindgen_utils::{ - impl_wasm_traits, prelude::*, serialize_hashmap_as_object, serialize_opt_hashmap_as_object, -}; - -#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct ConfigSource { - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub using_networks_from: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub networks: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub subgraphs: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub orderbooks: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub tokens: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub deployers: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub orders: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub scenarios: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub charts: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub deployments: HashMap, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub metaboards: HashMap, - #[serde(skip_serializing_if = "Option::is_none")] - pub sentry: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub raindex_version: Option, - #[serde(skip_serializing_if = "Option::is_none")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_opt_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub accounts: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub gui: Option, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(ConfigSource); - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type SubgraphCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type ScenarioCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type NetworkCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type DeployerCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type OrderCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type OrderbookCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type TokenCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type MetaboardCfgRef = String; - -#[cfg_attr(target_family = "wasm", tsify::declare)] -pub type DeploymentCfgRef = String; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct NetworkConfigSource { - #[cfg_attr(target_family = "wasm", tsify(type = "string"))] - pub rpc: Url, - pub chain_id: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub network_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub currency: Option, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(NetworkConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct RemoteNetworksConfigSource { - pub url: String, - pub format: String, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(RemoteNetworksConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct OrderbookConfigSource { - #[cfg_attr(target_family = "wasm", tsify(type = "string"))] - pub address: Address, - #[serde(skip_serializing_if = "Option::is_none")] - pub network: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub subgraph: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(OrderbookConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct TokenConfigSource { - pub network: NetworkCfgRef, - #[cfg_attr(target_family = "wasm", tsify(type = "string"))] - pub address: Address, - #[serde(skip_serializing_if = "Option::is_none")] - pub decimals: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub symbol: Option, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(TokenConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct DeployerConfigSource { - #[cfg_attr(target_family = "wasm", tsify(type = "string"))] - pub address: Address, - #[serde(skip_serializing_if = "Option::is_none")] - pub network: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub label: Option, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(DeployerConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct DeploymentConfigSource { - pub scenario: ScenarioCfgRef, - pub order: OrderCfgRef, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(DeploymentConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct IOStringConfigSource { - pub token: TokenCfgRef, - #[serde(skip_serializing_if = "Option::is_none")] - #[cfg_attr(target_family = "wasm", tsify(optional, type = "string"))] - pub vault_id: Option, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(IOStringConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct OrderConfigSource { - pub inputs: Vec, - pub outputs: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub deployer: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub orderbook: Option, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(OrderConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct ScenarioConfigSource { - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub bindings: HashMap, - #[serde(skip_serializing_if = "Option::is_none")] - pub runs: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub blocks: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub deployer: Option, - #[serde(skip_serializing_if = "Option::is_none")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_opt_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub scenarios: Option>, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(ScenarioConfigSource); - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] -#[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct ChartConfigSource { - #[serde(skip_serializing_if = "Option::is_none")] - pub scenario: Option, - #[serde(skip_serializing_if = "Option::is_none")] - #[cfg_attr( - target_family = "wasm", - serde(serialize_with = "serialize_opt_hashmap_as_object"), - tsify(optional, type = "Record") - )] - pub plots: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub metrics: Option>, -} -#[cfg(target_family = "wasm")] -impl_wasm_traits!(ChartConfigSource); - -#[derive(Error, Debug)] -pub enum ConfigSourceError { - #[error(transparent)] - YamlDeserializerError(#[from] serde_yaml::Error), - #[error(transparent)] - RemoteNetworkError(#[from] RemoteNetworkError), - #[error("Conflicting networks, a network with key '{}' already exists", 0)] - ConflictingNetworks(String), - #[error(transparent)] - ChainIdError(#[from] ChainIdError), -} - -impl ConfigSource { - pub async fn try_from_string( - val: String, - top_config: Option, - ) -> Result<(ConfigSource, ConfigSource), ConfigSourceError> { - if let Some(top_config) = top_config { - let merged = MergedConfigSource::new(val, top_config).await?; - Ok((merged.main, merged.top_config)) - } else { - let mut conf: ConfigSource = serde_yaml::from_str(&val)?; - if !conf.using_networks_from.is_empty() { - for (_key, item) in conf.using_networks_from.iter() { - let remote_networks = - RemoteNetworks::try_from_remote_network_config_source(item.clone()).await?; - match remote_networks { - RemoteNetworks::ChainId(chains) => { - for chain in &chains { - if conf.networks.iter().all(|(k, _v)| *k != chain.short_name) { - if let Ok(v) = chain.clone().try_into() { - conf.networks.insert(chain.short_name.clone(), v); - } - } else { - return Err(ConfigSourceError::ConflictingNetworks( - chain.name.clone(), - )); - } - } - } - } - } - } - Ok((conf, ConfigSource::default())) - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] -#[serde(rename_all = "kebab-case")] -struct MergedConfigSource { - main: ConfigSource, - top_config: ConfigSource, -} - -impl MergedConfigSource { - async fn new( - main_config: String, - top_config: String, - ) -> Result { - let mut main_indented = String::new(); - let mut top_config_indented = String::new(); - - // indent each line of the given ymls 1 level - // so they can go under a sep top key on a merged yml - // this ensures that keys dont collide and also is the - // safest since the original chars of each yml stay intact - main_config.lines().for_each(|line| { - main_indented.push_str(" "); - main_indented.push_str(line); - main_indented.push('\n'); - }); - top_config.lines().for_each(|line| { - top_config_indented.push_str(" "); - top_config_indented.push_str(line); - top_config_indented.push('\n'); - }); - - // top config can have anchors and main config that sits lower can - // ref them ie cant use a ref that its anchor comes after the fact - let merged = format!( - "top-config: -{} - -main: -{} -", - top_config_indented, main_indented - ); - let mut merged_conf: MergedConfigSource = serde_yaml::from_str(&merged)?; - - // handle remote networks for both ymls - if !merged_conf.main.using_networks_from.is_empty() { - for (_key, item) in merged_conf.main.using_networks_from.iter() { - let remote_networks = - RemoteNetworks::try_from_remote_network_config_source(item.clone()).await?; - match remote_networks { - RemoteNetworks::ChainId(chains) => { - for chain in &chains { - if merged_conf - .main - .networks - .iter() - .all(|(k, _v)| *k != chain.short_name) - { - if let Ok(v) = chain.clone().try_into() { - merged_conf - .main - .networks - .insert(chain.short_name.clone(), v); - } - } else { - return Err(ConfigSourceError::ConflictingNetworks( - chain.name.clone(), - )); - } - } - } - } - } - } - if !merged_conf.top_config.using_networks_from.is_empty() { - for (_key, item) in merged_conf.top_config.using_networks_from.iter() { - let remote_networks = - RemoteNetworks::try_from_remote_network_config_source(item.clone()).await?; - match remote_networks { - RemoteNetworks::ChainId(chains) => { - for chain in &chains { - if merged_conf - .top_config - .networks - .iter() - .all(|(k, _v)| *k != chain.short_name) - { - if let Ok(v) = chain.clone().try_into() { - merged_conf - .top_config - .networks - .insert(chain.short_name.clone(), v); - } - } else { - return Err(ConfigSourceError::ConflictingNetworks( - chain.name.clone(), - )); - } - } - } - } - } - } - Ok(merged_conf) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use httpmock::{Method::GET, MockServer}; - use serde_json::json; - - #[tokio::test] - async fn parse_yaml_into_configstrings() { - let mocked_chain_id_server = MockServer::start_async().await; - let yaml_data = format!( - r#" -raindex-version: 123 - -using-networks-from: - chainid: - url: {} - format: chainid - -networks: - mainnet: - rpc: https://mainnet.node - chain-id: 1 - label: Mainnet - network-id: 1 - currency: ETH - testnet: - rpc: https://testnet.node - chain-id: 2 - label: Testnet - network-id: 2 - currency: ETH - -subgraphs: - mainnet: https://mainnet.subgraph - testnet: https://testnet.subgraph - -orderbooks: - mainnetOrderbook: - address: 0xabc0000000000000000000000000000000000001 - network: mainnet - subgraph: mainnet - label: Mainnet Orderbook - testnetOrderbook: - address: 0xabc0000000000000000000000000000000000002 - network: testnet - subgraph: testnet - label: Testnet Orderbook - -tokens: - eth: - network: mainnet - address: 0xabc0000000000000000000000000000000000003 - decimals: 18 - label: Ethereum - symbol: ETH - dai: - network: mainnet - address: 0xabc0000000000000000000000000000000000004 - decimals: 18 - label: Dai - symbol: DAI - -deployers: - mainDeployer: - address: 0xabc0000000000000000000000000000000000005 - network: mainnet - label: Main Deployer - testDeployer: - address: 0xabc0000000000000000000000000000000000006 - network: testnet - label: Test Deployer - -orders: - buyETH: - inputs: - - token: eth - - token: dai - outputs: - - token: dai - vault-id: 3 - deployer: mainDeployer - orderbook: mainnetOrderbook - -scenarios: - mainScenario: - bindings: - key1: value1 - key2: value2 - runs: 100 - network: mainnet - deployer: mainDeployer - scenarios: - subScenario1: - bindings: - key3: value3 - subScenario2: - bindings: - key4: value4 -charts: - mainChart: - scenario: mainScenario - metrics: - - label: A metric - description: A description - unit-prefix: $ - unit-suffix: USD - value: 0.1 - - label: Another metric - unit-suffix: ETH - value: 0.2 - - label: Yet another metric - unit-prefix: £ - value: 0.3 - plots: - plot1: - title: "My plot" - subtitle: "My subtitle" - marks: - - type: dot - options: - x: "0.1" - y: "0.2" - stroke: "black" - plot2: - title: "Hexbin" - marks: - - type: dot - options: - transform: - type: hexbin - content: - outputs: - fill: count - options: - x: 0.1 - y: 0.2 - bin-width: 10 -deployments: - first-deployment: - scenario: mainScenario - order: sellETH - second-deployment: - scenario: mainScenario - order: buyETH - -sentry: true - -accounts: - name-one: address-one - name-two: address-two - -gui: - name: Fixed limit - description: Fixed limit order strategy - deployments: - some-deployment: - name: Buy WETH with USDC on Base. - description: Buy WETH with USDC for fixed price on Base network. - deposits: - - token: token1 - min: 0 - presets: - - "0" - - "10" - - "100" - - "1000" - - "10000" - fields: - - binding: binding-1 - name: Field 1 name - description: Field 1 description - presets: - - name: Preset 1 - value: "0x1234567890abcdef1234567890abcdef12345678" - - name: Preset 2 - value: "false" - - name: Preset 3 - value: "some-string" - - binding: binding-2 - name: Field 2 name - description: Field 2 description - min: 100 - presets: - - value: "99.2" - - value: "582.1" - - value: "648.239" -"#, - mocked_chain_id_server.url("/json") - ); - - let mocked_chain_id_response = json!([ - { - "name": "Ethereum Mainnet", - "chain": "ETH", - "rpc": ["https://abcd.com/v3/${API_KEY}","https://api.mycryptoapi.com/eth","https://cloudflare-eth.com"], - "nativeCurrency": {"name": "Ether","symbol": "ETH","decimals": 18}, - "infoURL": "https://ethereum.org", - "shortName": "eth", - "chainId": 1, - "networkId": 1 - }, - { - "name": "Polygon Mainnet", - "chain": "Polygon", - "rpc": ["https://polygon-rpc.com/","wss://polygon.drpc.org"], - "nativeCurrency": {"name": "MATIC","symbol": "MATIC","decimals": 18}, - "infoURL": "https://polygon.technology/", - "shortName": "matic", - "chainId": 137, - "networkId": 137 - } - ]); - mocked_chain_id_server.mock(|when, then| { - when.method(GET).path("/json"); - then.json_body_obj(&mocked_chain_id_response); - }); - - let config = ConfigSource::try_from_string(yaml_data, None) - .await - .unwrap() - .0; - - // Asserting a few values to verify successful parsing - assert_eq!( - config.clone().networks.get("mainnet").unwrap().rpc, - Url::parse("https://mainnet.node").unwrap() - ); - assert_eq!( - config.networks.get("mainnet").unwrap().label, - Some("Mainnet".into()) - ); - assert_eq!( - config.subgraphs.get("mainnet"), - Some(&Url::parse("https://mainnet.subgraph").unwrap()) - ); - assert_eq!( - config.orderbooks.get("mainnetOrderbook").unwrap().address, - "0xabc0000000000000000000000000000000000001" - .parse::
() - .unwrap() - ); - assert_eq!(config.tokens.get("eth").unwrap().decimals, Some(18)); - assert!(config.sentry.unwrap()); - - // remote networks fetched from remote source and converted and added to networks - assert_eq!( - config.clone().networks.get("eth").unwrap().rpc, - Url::parse("https://api.mycryptoapi.com/eth").unwrap() - ); - assert_eq!( - config.networks.get("eth").unwrap().label, - Some("Ethereum Mainnet".into()) - ); - assert_eq!( - config.clone().networks.get("matic").unwrap().rpc, - Url::parse("https://polygon-rpc.com/").unwrap() - ); - assert_eq!( - config.networks.get("matic").unwrap().label, - Some("Polygon Mainnet".into()) - ); - - let expected_order = OrderConfigSource { - inputs: vec![ - IOStringConfigSource { - token: "eth".to_string(), - vault_id: None, - }, - IOStringConfigSource { - token: "dai".to_string(), - vault_id: None, - }, - ], - outputs: vec![IOStringConfigSource { - token: "dai".to_string(), - vault_id: Some(U256::from(3)), - }], - deployer: Some("mainDeployer".to_string()), - orderbook: Some("mainnetOrderbook".to_string()), - }; - let order = config.orders.get("buyETH").unwrap(); - assert_eq!(order.inputs[0].token, expected_order.inputs[0].token); - assert_eq!(order.inputs[0].vault_id, expected_order.inputs[0].vault_id); - assert_eq!(order.inputs[1].token, expected_order.inputs[1].token); - assert_eq!(order.inputs[1].vault_id, expected_order.inputs[1].vault_id); - assert_eq!(order.outputs[0].token, expected_order.outputs[0].token); - assert_eq!( - order.outputs[0].vault_id, - expected_order.outputs[0].vault_id - ); - assert_eq!(order.deployer, expected_order.deployer); - assert_eq!(order.orderbook, expected_order.orderbook); - - assert_eq!(config.raindex_version, Some("123".to_string())); - - let accounts = config.accounts.unwrap(); - assert_eq!(accounts.get("name-one").unwrap(), "address-one"); - assert_eq!(accounts.get("name-two").unwrap(), "address-two"); - - let gui = config.gui.unwrap(); - assert_eq!(gui.name, "Fixed limit"); - assert_eq!(gui.description, "Fixed limit order strategy"); - assert_eq!(gui.deployments.len(), 1); - let deployment = gui.deployments.get("some-deployment").unwrap(); - assert_eq!(deployment.name, "Buy WETH with USDC on Base."); - assert_eq!( - deployment.description, - "Buy WETH with USDC for fixed price on Base network." - ); - assert_eq!(deployment.deposits.len(), 1); - let deposit = &deployment.deposits[0]; - assert_eq!(deposit.token, "token1".to_string()); - let presets = deposit.presets.as_ref().unwrap(); - assert_eq!(presets.len(), 5); - assert_eq!(presets[0], "0".to_string()); - assert_eq!(presets[1], "10".to_string()); - assert_eq!(presets[2], "100".to_string()); - assert_eq!(presets[3], "1000".to_string()); - assert_eq!(presets[4], "10000".to_string()); - assert_eq!(deployment.fields.len(), 2); - let field = &deployment.fields[0]; - assert_eq!(field.binding, "binding-1"); - assert_eq!(field.name, "Field 1 name"); - assert_eq!(field.description, Some("Field 1 description".to_string())); - let presets = field.presets.as_ref().unwrap(); - assert_eq!(presets.len(), 3); - assert_eq!(presets[0].name, Some("Preset 1".to_string())); - assert_eq!( - presets[0].value, - "0x1234567890abcdef1234567890abcdef12345678" - ); - assert_eq!(presets[1].name, Some("Preset 2".to_string())); - assert_eq!(presets[1].value, "false".to_string()); - assert_eq!(presets[2].name, Some("Preset 3".to_string())); - assert_eq!(presets[2].value, "some-string".to_string()); - let field = &deployment.fields[1]; - assert_eq!(field.binding, "binding-2"); - assert_eq!(field.name, "Field 2 name"); - assert_eq!(field.description, Some("Field 2 description".to_string())); - let presets = field.presets.as_ref().unwrap(); - assert_eq!(presets.len(), 3); - assert_eq!(presets[0].value, "99.2".to_string()); - assert_eq!(presets[1].value, "582.1".to_string()); - assert_eq!(presets[2].value, "648.239".to_string()); - } - - #[tokio::test] - async fn test_remote_chain_configstrings_unhappy() { - let mocked_chain_id_server = MockServer::start_async().await; - let yaml_data = format!( - r#" -using-networks-from: - chainid: - url: {} - format: chainid"#, - mocked_chain_id_server.url("/json") - ); - - let mocked_chain_id_response = json!([ - { - "name": "Ethereum Mainnet", - "chain": "ETH", - "rpc": ["https://abcd.com, wss://abcd.com/ws"], - "nativeCurrency": {"name": "Ether","symbol": "ETH","decimals": 18}, - "infoURL": "https://ethereum.org", - "shortName": "eth", - "chainId": 1, - "networkId": 1 - } - ]); - mocked_chain_id_server.mock(|when, then| { - when.method(GET).path("/json"); - then.json_body_obj(&mocked_chain_id_response); - }); - - let config = ConfigSource::try_from_string(yaml_data, None) - .await - .expect_err("expected to fail"); - matches!(config, ConfigSourceError::ChainIdError(_)); - } - - #[tokio::test] - async fn parse_yaml_into_configstrings_with_anchors() { - let top_yml_data = r#" -raindex-version: &raindex 123 -networks: - mainnet: &mainnet - rpc: https://mainnet.node - chain-id: 1 - label: Mainnet - network-id: 1 - currency: ETH - testnet: &testnet - rpc: https://testnet.node - chain-id: 2 - label: Testnet - network-id: 2 - currency: ETH -subgraphs: &subgraphs - mainnet: https://mainnet.subgraph - testnet: https://testnet.subgraph -orderbooks: &orderbooks - mainnetOrderbook: - address: 0xabc0000000000000000000000000000000000001 - network: mainnet - subgraph: mainnet - label: Mainnet Orderbook -"#; - - let yaml_data = r#" -raindex-version: *raindex -networks: - mainnet: *mainnet - testnet: *testnet -subgraphs: *subgraphs -orderbooks: *orderbooks -"#; - - let (config, top_config) = - ConfigSource::try_from_string(yaml_data.to_string(), Some(top_yml_data.to_string())) - .await - .unwrap(); - - // Asserting a few values to verify successful parsing for config - assert_eq!(config.clone().raindex_version.unwrap(), "123".to_string()); - assert_eq!( - config.clone().networks.get("mainnet").unwrap().rpc, - Url::parse("https://mainnet.node").unwrap() - ); - assert_eq!( - config.networks.get("mainnet").unwrap().label, - Some("Mainnet".into()) - ); - assert_eq!( - config.subgraphs.get("mainnet"), - Some(&Url::parse("https://mainnet.subgraph").unwrap()) - ); - assert_eq!( - config.orderbooks.get("mainnetOrderbook").unwrap().address, - "0xabc0000000000000000000000000000000000001" - .parse::
() - .unwrap() - ); - - // Asserting a few values to verify successful parsing for other config - assert_eq!( - top_config.clone().raindex_version.unwrap(), - "123".to_string() - ); - assert_eq!( - top_config.clone().networks.get("mainnet").unwrap().rpc, - Url::parse("https://mainnet.node").unwrap() - ); - assert_eq!( - top_config.networks.get("mainnet").unwrap().label, - Some("Mainnet".into()) - ); - assert_eq!( - top_config.subgraphs.get("mainnet"), - Some(&Url::parse("https://mainnet.subgraph").unwrap()) - ); - assert_eq!( - top_config - .orderbooks - .get("mainnetOrderbook") - .unwrap() - .address, - "0xabc0000000000000000000000000000000000001" - .parse::
() - .unwrap() - ); - - // in this case both configs should be equal - assert_eq!(config, top_config); - } -} diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 7532e43ef..4d7093e45 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -1,7 +1,6 @@ pub mod blocks; pub mod chart; pub mod config; -pub mod config_source; pub mod deployer; pub mod deployment; pub mod gui; @@ -23,7 +22,6 @@ pub mod unit_test; pub mod yaml; pub(crate) use chart::*; -pub(crate) use config_source::*; pub(crate) use deployer::*; pub(crate) use deployment::*; pub(crate) use gui::*; From 938d22326668df58d5417fecced06b0f34c081fd Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:23:08 +0300 Subject: [PATCH 02/51] update scenario config --- crates/settings/src/scenario.rs | 303 +++----------------------------- crates/settings/src/yaml/mod.rs | 11 +- tauri-app/src-tauri/Cargo.lock | 40 ++--- 3 files changed, 45 insertions(+), 309 deletions(-) diff --git a/crates/settings/src/scenario.rs b/crates/settings/src/scenario.rs index 3889b00d1..93d334a2a 100644 --- a/crates/settings/src/scenario.rs +++ b/crates/settings/src/scenario.rs @@ -38,18 +38,16 @@ pub struct ScenarioCfg { impl_wasm_traits!(ScenarioCfg); impl ScenarioCfg { - pub fn validate_runs(value: &str) -> Result { + pub fn validate_runs(value: &str) -> Result { value .parse::() - .map_err(ParseScenarioConfigSourceError::RunsParseError) + .map_err(ParseScenarioCfgError::RunsParseError) } - pub fn validate_blocks(value: &str) -> Result { + pub fn validate_blocks(value: &str) -> Result { match serde_yaml::from_str::(value) { Ok(blocks) => Ok(blocks), - Err(_) => Err(ParseScenarioConfigSourceError::BlocksParseError( - value.to_string(), - )), + Err(_) => Err(ParseScenarioCfgError::BlocksParseError(value.to_string())), } } @@ -94,8 +92,8 @@ impl ScenarioCfg { if let Some(parent_value) = parent_scenario.bindings.as_ref().and_then(|pb| pb.get(&k)) { if *parent_value != v { - return Err(YamlError::ParseScenarioConfigSourceError( - ParseScenarioConfigSourceError::ParentBindingShadowedError(k.to_string()), + return Err(YamlError::ParseScenarioCfgError( + ParseScenarioCfgError::ParentBindingShadowedError(k.to_string()), )); } } @@ -155,8 +153,8 @@ impl ScenarioCfg { if let Some(current_deployer) = current_deployer { if let Some(parent_deployer) = parent_scenario.deployer.as_ref() { if current_deployer.key != parent_deployer.key { - return Err(YamlError::ParseScenarioConfigSourceError( - ParseScenarioConfigSourceError::ParentDeployerShadowedError( + return Err(YamlError::ParseScenarioCfgError( + ParseScenarioCfgError::ParentDeployerShadowedError( current_deployer.key.clone(), ), )); @@ -184,9 +182,9 @@ impl ScenarioCfg { bindings: bindings.clone(), runs, blocks, - deployer: deployer.clone().ok_or( - ParseScenarioConfigSourceError::DeployerNotFound(scenario_key), - )?, + deployer: deployer + .clone() + .ok_or(ParseScenarioCfgError::DeployerNotFound(scenario_key))?, }, ); @@ -383,8 +381,8 @@ impl YamlParsableHash for ScenarioCfg { )?; if deployer.is_none() { - return Err(YamlError::ParseScenarioConfigSourceError( - ParseScenarioConfigSourceError::DeployerNotFound(scenario_key), + return Err(YamlError::ParseScenarioCfgError( + ParseScenarioCfgError::DeployerNotFound(scenario_key), )); } } @@ -426,7 +424,7 @@ impl PartialEq for ScenarioCfg { } #[derive(Error, Debug, PartialEq)] -pub enum ParseScenarioConfigSourceError { +pub enum ParseScenarioCfgError { #[error("Failed to parse runs")] RunsParseError(ParseIntError), #[error("Parent binding shadowed by child: {0}")] @@ -441,20 +439,20 @@ pub enum ParseScenarioConfigSourceError { BlocksParseError(String), } -impl ParseScenarioConfigSourceError { +impl ParseScenarioCfgError { pub fn to_readable_msg(&self) -> String { match self { - ParseScenarioConfigSourceError::RunsParseError(err) => + ParseScenarioCfgError::RunsParseError(err) => format!("The 'runs' value in your scenario YAML configuration must be a valid number: {}", err), - ParseScenarioConfigSourceError::ParentBindingShadowedError(binding) => + ParseScenarioCfgError::ParentBindingShadowedError(binding) => format!("Binding conflict in your YAML configuration: The child scenario is trying to override the binding '{}' that was already defined in a parent scenario. Child scenarios cannot change binding values defined by parents.", binding), - ParseScenarioConfigSourceError::ParentDeployerShadowedError(deployer) => + ParseScenarioCfgError::ParentDeployerShadowedError(deployer) => format!("Deployer conflict in your YAML configuration: The child scenario is trying to use deployer '{}' which differs from the deployer specified in the parent scenario. Child scenarios must use the same deployer as their parent.", deployer), - ParseScenarioConfigSourceError::DeployerNotFound(scenario) => + ParseScenarioCfgError::DeployerNotFound(scenario) => format!("No deployer was found for scenario '{}' in your YAML configuration. Please specify a deployer for this scenario or ensure it inherits one from a parent scenario.", scenario), - ParseScenarioConfigSourceError::ParentOrderbookShadowedError(orderbook) => + ParseScenarioCfgError::ParentOrderbookShadowedError(orderbook) => format!("Orderbook conflict in your YAML configuration: The child scenario is trying to use orderbook '{}' which differs from the orderbook specified in the parent scenario. Child scenarios must use the same orderbook as their parent.", orderbook), - ParseScenarioConfigSourceError::BlocksParseError(blocks) => + ParseScenarioCfgError::BlocksParseError(blocks) => format!("Failed to parse the 'blocks' configuration in your YAML: {}. Please ensure it follows the correct format.", blocks), } } @@ -467,257 +465,12 @@ pub struct ScenarioParent { deployer: Option>, } -// Shadowing is disallowed for deployers, orderbooks and specific bindings. -// If a child specifies one that is already set by the parent, this is an error. -// -// Nested scenarios within the ScenarioConfigSource struct are flattened out into a -// hashmap of scenarios, where the key is the path such as foo.bar.baz. -// Every level of the scenario path inherits its parents bindings recursively. -impl ScenarioConfigSource { - pub fn try_into_scenarios( - &self, - name: String, - parent: &ScenarioParent, - deployers: &HashMap>, - ) -> Result>, ParseScenarioConfigSourceError> { - // Determine the resolved name for the deployer, preferring the explicit deployer name if provided. - let resolved_name = self.deployer.as_ref().unwrap_or(&name); - - // Attempt to find the deployer using the resolved name. - let resolved_deployer = deployers.get(resolved_name); - - // If no deployer is found using the resolved name, fall back to the parent's deployer, if any. - let deployer_ref = resolved_deployer.or(parent.deployer.as_ref()); - - // If no deployer could be resolved and there's no parent deployer, return an error. - let deployer_ref = deployer_ref - .ok_or_else(|| ParseScenarioConfigSourceError::DeployerNotFound(name.clone()))?; - - // Check for non-matching override: if both the current and parent deployers are present and different, it's an error. - if let (deployer, Some(parent_deployer)) = (deployer_ref, parent.deployer.as_ref()) { - if deployer.key != parent_deployer.key { - return Err(ParseScenarioConfigSourceError::ParentDeployerShadowedError( - resolved_name.clone(), - )); - } - } - - // Merge bindings and check for shadowing - let mut bindings = parent - .bindings - .as_ref() - .map_or_else(HashMap::new, |pb| pb.clone()); - for (k, v) in &self.bindings { - if let Some(parent_value) = parent.bindings.as_ref().and_then(|pb| pb.get(k)) { - if parent_value != v { - return Err(ParseScenarioConfigSourceError::ParentBindingShadowedError( - k.to_string(), - )); - } - } - bindings.insert(k.to_string(), v.to_string()); - } - - // Create and add the parent scenario for this level - let parent_scenario = Arc::new(ScenarioCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: name.clone(), - bindings: bindings.clone(), - runs: self.runs, - blocks: self.blocks.clone(), - deployer: deployer_ref.clone(), - }); - - let mut scenarios = HashMap::new(); - scenarios.insert(name.clone(), parent_scenario); - - // Recursively add child scenarios - if let Some(scenarios_map) = &self.scenarios { - for (child_name, child_scenario) in scenarios_map { - let child_scenarios = child_scenario.try_into_scenarios( - format!("{}.{}", name, child_name), - &ScenarioParent { - key: "".to_string(), - bindings: Some(bindings.clone()), - deployer: Some(deployer_ref.clone()), - }, - deployers, - )?; - - scenarios.extend(child_scenarios); - } - } - - Ok(scenarios) - } -} - #[cfg(test)] mod tests { use super::*; - use crate::{ - blocks::{BlockCfg, BlockRangeCfg}, - test::mock_deployer, - }; - use alloy::primitives::Address; - use std::collections::HashMap; - use url::Url; + use crate::blocks::{BlockCfg, BlockRangeCfg}; use yaml::tests::get_document; - #[test] - fn test_scenarios_conversion_with_nesting() { - // Initialize networks as in the previous example - let mut networks = HashMap::new(); - networks.insert( - "mainnet".to_string(), - NetworkConfigSource { - rpc: Url::parse("https://mainnet.node").unwrap(), - chain_id: 1, - label: Some("Ethereum Mainnet".to_string()), - network_id: Some(1), - currency: Some("ETH".to_string()), - }, - ); - - // Define a deployer - let mut deployers = HashMap::new(); - deployers.insert( - "mainnet".to_string(), - DeployerConfigSource { - address: "0xabcdef0123456789ABCDEF0123456789ABCDEF01" - .parse::
() - .unwrap(), - network: None, - label: Some("Mainnet Deployer".to_string()), - }, - ); - - // Define nested scenarios - let mut nested_scenario2 = HashMap::new(); - nested_scenario2.insert( - "nested_scenario2".to_string(), - ScenarioConfigSource { - bindings: HashMap::new(), // Assuming no bindings for simplification - runs: Some(2), - blocks: None, - deployer: None, - scenarios: None, // No further nesting - }, - ); - - let mut nested_scenario1 = HashMap::new(); - nested_scenario1.insert( - "nested_scenario1".to_string(), - ScenarioConfigSource { - bindings: HashMap::new(), // Assuming no bindings for simplification - runs: Some(5), - blocks: None, - deployer: None, - scenarios: Some(nested_scenario2), // Include nested_scenario2 - }, - ); - - // Define root scenario with nested_scenario1 - let mut scenarios = HashMap::new(); - scenarios.insert( - "root_scenario".to_string(), - ScenarioConfigSource { - bindings: HashMap::new(), // Assuming no bindings for simplification - runs: Some(10), - blocks: None, - deployer: Some("mainnet".to_string()), - scenarios: Some(nested_scenario1), // Include nested_scenario1 - }, - ); - - // Construct ConfigSource with the above scenarios - let config_string = ConfigSource { - raindex_version: None, - using_networks_from: HashMap::new(), - networks, - subgraphs: HashMap::new(), // Assuming no subgraphs for simplification - metaboards: HashMap::new(), // Assuming no metaboards for simplification - orderbooks: HashMap::new(), // Assuming no orderbooks for simplification - tokens: HashMap::new(), // Assuming no tokens for simplification - deployers, - orders: HashMap::new(), // Assuming no orders for simplification - scenarios, - charts: HashMap::new(), // Assuming no charts for simplification - deployments: HashMap::new(), - sentry: None, - accounts: None, // Assuming no accounts for simplification - gui: None, - }; - - // Perform the conversion - let config_result = Config::try_from(config_string); - assert!(config_result.is_ok()); - - let config = config_result.unwrap(); - - // Verify the root scenario - assert!(config.scenarios.contains_key("root_scenario")); - let root_scenario = config.scenarios.get("root_scenario").unwrap(); - assert_eq!(root_scenario.runs, Some(10)); - - // Verify the first level of nested scenarios - assert!(config - .scenarios - .contains_key("root_scenario.nested_scenario1")); - let nested_scenario1 = config - .scenarios - .get("root_scenario.nested_scenario1") - .unwrap(); - assert_eq!(nested_scenario1.runs, Some(5)); - - // Verify the second level of nested scenarios - assert!(config - .scenarios - .contains_key("root_scenario.nested_scenario1.nested_scenario2")); - let nested_scenario2 = config - .scenarios - .get("root_scenario.nested_scenario1.nested_scenario2") - .unwrap(); - assert_eq!(nested_scenario2.runs, Some(2)); - } - - #[test] - fn test_scenario_shadowing_error_in_bindings() { - let parent_bindings = - HashMap::from([("shared_key".to_string(), "parent_value".to_string())]); - - let parent_scenario = ScenarioParent { - key: "".to_string(), - bindings: Some(parent_bindings), - deployer: Some(mock_deployer()), - }; - - let mut child_bindings = HashMap::new(); - child_bindings.insert("shared_key".to_string(), "child_value".to_string()); // Intentionally shadowing parent binding - - let child_scenario = ScenarioConfigSource { - bindings: child_bindings, - runs: None, - blocks: None, - deployer: None, - scenarios: None, - }; - - let result = child_scenario.try_into_scenarios( - "child".to_string(), - &parent_scenario, - &HashMap::new(), // Empty deployers for simplification - ); - - assert!(result.is_err()); - match result.err().unwrap() { - ParseScenarioConfigSourceError::ParentBindingShadowedError(key) => { - assert_eq!(key, "shared_key"); - } - _ => panic!("Expected ParentBindingShadowedError"), - } - } - #[test] fn test_parse_scenarios_from_yaml() { let yaml = r#" @@ -828,9 +581,9 @@ scenarios: let error = ScenarioCfg::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error.to_string(), - YamlError::ParseScenarioConfigSourceError( - ParseScenarioConfigSourceError::ParentBindingShadowedError("key1".to_string()) - ) + YamlError::ParseScenarioCfgError(ParseScenarioCfgError::ParentBindingShadowedError( + "key1".to_string() + )) .to_string() ); assert_eq!(error.to_readable_msg(), "Scenario configuration error in your YAML: Binding conflict in your YAML configuration: The child scenario is trying to override the binding 'key1' that was already defined in a parent scenario. Child scenarios cannot change binding values defined by parents."); @@ -864,9 +617,9 @@ scenarios: let error = ScenarioCfg::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error.to_string(), - YamlError::ParseScenarioConfigSourceError( - ParseScenarioConfigSourceError::ParentDeployerShadowedError("testnet".to_string()) - ) + YamlError::ParseScenarioCfgError(ParseScenarioCfgError::ParentDeployerShadowedError( + "testnet".to_string() + )) .to_string() ); assert_eq!(error.to_readable_msg(), "Scenario configuration error in your YAML: Deployer conflict in your YAML configuration: The child scenario is trying to use deployer 'testnet' which differs from the deployer specified in the parent scenario. Child scenarios must use the same deployer as their parent."); diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index 7c06b4597..34fbff04d 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -6,7 +6,7 @@ pub mod orderbook; use crate::{ NetworkCfg, ParseDeployerConfigSourceError, ParseDeploymentConfigSourceError, ParseNetworkConfigSourceError, ParseOrderConfigSourceError, ParseOrderbookConfigSourceError, - ParseScenarioConfigSourceError, ParseTokenConfigSourceError, TokenCfg, + ParseScenarioCfgError, ParseTokenConfigSourceError, TokenCfg, }; use alloy::primitives::ruint::ParseError as RuintParseError; use context::{Context, ContextError}; @@ -184,7 +184,7 @@ pub enum YamlError { #[error(transparent)] ParseOrderConfigSourceError(#[from] ParseOrderConfigSourceError), #[error(transparent)] - ParseScenarioConfigSourceError(#[from] ParseScenarioConfigSourceError), + ParseScenarioCfgError(#[from] ParseScenarioCfgError), #[error(transparent)] ParseDeploymentConfigSourceError(#[from] ParseDeploymentConfigSourceError), #[error(transparent)] @@ -236,10 +236,7 @@ impl PartialEq for YamlError { (Self::ParseOrderConfigSourceError(e1), Self::ParseOrderConfigSourceError(e2)) => { e1 == e2 } - ( - Self::ParseScenarioConfigSourceError(e1), - Self::ParseScenarioConfigSourceError(e2), - ) => e1 == e2, + (Self::ParseScenarioCfgError(e1), Self::ParseScenarioCfgError(e2)) => e1 == e2, ( Self::ParseDeploymentConfigSourceError(e1), Self::ParseDeploymentConfigSourceError(e2), @@ -318,7 +315,7 @@ impl YamlError { "Order configuration error in your YAML: {}", err.to_readable_msg() ), - YamlError::ParseScenarioConfigSourceError(err) => format!( + YamlError::ParseScenarioCfgError(err) => format!( "Scenario configuration error in your YAML: {}", err.to_readable_msg() ), diff --git a/tauri-app/src-tauri/Cargo.lock b/tauri-app/src-tauri/Cargo.lock index 21b4a92c5..897f41618 100644 --- a/tauri-app/src-tauri/Cargo.lock +++ b/tauri-app/src-tauri/Cargo.lock @@ -6103,10 +6103,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -8128,7 +8129,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "wasm-bindgen-utils 0.0.6", + "wasm-bindgen-utils", ] [[package]] @@ -8174,7 +8175,7 @@ dependencies = [ "tracing-subscriber", "url", "validator", - "wasm-bindgen-utils 0.0.6", + "wasm-bindgen-utils", ] [[package]] @@ -8276,7 +8277,7 @@ dependencies = [ "strict-yaml-rust", "thiserror", "url", - "wasm-bindgen-utils 0.0.7", + "wasm-bindgen-utils", ] [[package]] @@ -8285,7 +8286,7 @@ version = "0.0.0-alpha.0" dependencies = [ "alloy", "serde", - "wasm-bindgen-utils 0.0.7", + "wasm-bindgen-utils", ] [[package]] @@ -8322,7 +8323,7 @@ dependencies = [ "tokio", "tracing", "url", - "wasm-bindgen-utils 0.0.7", + "wasm-bindgen-utils", ] [[package]] @@ -8355,7 +8356,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", - "wasm-bindgen-utils 0.0.7", + "wasm-bindgen-utils", ] [[package]] @@ -8376,7 +8377,7 @@ dependencies = [ "serde_json", "thiserror", "url", - "wasm-bindgen-utils 0.0.7", + "wasm-bindgen-utils", ] [[package]] @@ -10493,7 +10494,7 @@ dependencies = [ "tokio", "url", "uuid 1.10.0", - "wasm-bindgen-utils 0.0.6", + "wasm-bindgen-utils", ] [[package]] @@ -11758,21 +11759,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-bindgen-utils" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04293ba23e84c21b0d42179abedf35e6eb082bd6f06bce7a42a8dd8e0b2b589f" -dependencies = [ - "js-sys", - "paste", - "serde", - "serde-wasm-bindgen 0.6.5", - "tsify", - "wasm-bindgen", - "wasm-bindgen-futures", -] - [[package]] name = "wasm-bindgen-utils" version = "0.0.7" @@ -11802,9 +11788,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", From a07721c24d49170bb78018acc1d87e683f3eea0c Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:25:03 +0300 Subject: [PATCH 03/51] update network config --- crates/settings/src/network.rs | 68 +++++++-------------------------- crates/settings/src/yaml/mod.rs | 10 ++--- 2 files changed, 18 insertions(+), 60 deletions(-) diff --git a/crates/settings/src/network.rs b/crates/settings/src/network.rs index 635bb6acb..747cb5887 100644 --- a/crates/settings/src/network.rs +++ b/crates/settings/src/network.rs @@ -1,4 +1,3 @@ -use crate::config_source::*; use crate::yaml::context::Context; use crate::yaml::{ default_document, optional_string, require_hash, require_string, FieldErrorKind, YamlError, @@ -49,18 +48,18 @@ impl NetworkCfg { } } - pub fn validate_rpc(value: &str) -> Result { - Url::parse(value).map_err(ParseNetworkConfigSourceError::RpcParseError) + pub fn validate_rpc(value: &str) -> Result { + Url::parse(value).map_err(ParseNetworkCfgError::RpcParseError) } - pub fn validate_chain_id(value: &str) -> Result { + pub fn validate_chain_id(value: &str) -> Result { value .parse::() - .map_err(ParseNetworkConfigSourceError::ChainIdParseError) + .map_err(ParseNetworkCfgError::ChainIdParseError) } - pub fn validate_network_id(value: &str) -> Result { + pub fn validate_network_id(value: &str) -> Result { value .parse::() - .map_err(ParseNetworkConfigSourceError::NetworkIdParseError) + .map_err(ParseNetworkCfgError::NetworkIdParseError) } pub fn update_rpc(&mut self, rpc: &str) -> Result { @@ -215,8 +214,8 @@ impl YamlParsableHash for NetworkCfg { if let Some(yaml_cache) = &context.yaml_cache { for (key, network) in &yaml_cache.remote_networks { if networks.contains_key(key) { - return Err(YamlError::ParseNetworkConfigSourceError( - ParseNetworkConfigSourceError::RemoteNetworkKeyShadowing(key.clone()), + return Err(YamlError::ParseNetworkCfgError( + ParseNetworkCfgError::RemoteNetworkKeyShadowing(key.clone()), )); } networks.insert(key.clone(), network.clone()); @@ -252,7 +251,7 @@ impl PartialEq for NetworkCfg { } #[derive(Error, Debug, PartialEq)] -pub enum ParseNetworkConfigSourceError { +pub enum ParseNetworkCfgError { #[error("Failed to parse rpc: {}", 0)] RpcParseError(ParseError), #[error("Failed to parse chain_id: {}", 0)] @@ -263,22 +262,22 @@ pub enum ParseNetworkConfigSourceError { RemoteNetworkKeyShadowing(String), } -impl ParseNetworkConfigSourceError { +impl ParseNetworkCfgError { pub fn to_readable_msg(&self) -> String { match self { - ParseNetworkConfigSourceError::RpcParseError(err) => format!( + ParseNetworkCfgError::RpcParseError(err) => format!( "The RPC URL in your network configuration is invalid: {}", err ), - ParseNetworkConfigSourceError::ChainIdParseError(err) => format!( + ParseNetworkCfgError::ChainIdParseError(err) => format!( "The chain ID in your network configuration must be a valid number: {}", err ), - ParseNetworkConfigSourceError::NetworkIdParseError(err) => format!( + ParseNetworkCfgError::NetworkIdParseError(err) => format!( "The network ID in your network configuration must be a valid number: {}", err ), - ParseNetworkConfigSourceError::RemoteNetworkKeyShadowing(key) => format!( + ParseNetworkCfgError::RemoteNetworkKeyShadowing(key) => format!( "The remote network key '{}' is already defined in network configuration", key ), @@ -286,51 +285,12 @@ impl ParseNetworkConfigSourceError { } } -impl NetworkConfigSource { - pub fn try_into_network( - self, - key: String, - ) -> Result { - Ok(NetworkCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key, - rpc: self.rpc, - chain_id: self.chain_id, - label: self.label, - network_id: self.network_id, - currency: self.currency, - }) - } -} - #[cfg(test)] mod tests { use super::*; use crate::yaml::tests::get_document; use url::Url; - #[test] - fn test_try_from_network_string_success() { - let network_string = NetworkConfigSource { - rpc: Url::parse("http://127.0.0.1:8545").unwrap(), - chain_id: 1, - network_id: Some(1), - label: Some("Local Testnet".into()), - currency: Some("ETH".into()), - }; - - let result = network_string.try_into_network("local".into()); - assert!(result.is_ok()); - let network = result.unwrap(); - - assert_eq!(network.rpc, Url::parse("http://127.0.0.1:8545").unwrap()); - assert_eq!(network.chain_id, 1); - assert_eq!(network.network_id, Some(1)); - assert_eq!(network.label, Some("Local Testnet".into())); - assert_eq!(network.currency, Some("ETH".into())); - assert_eq!(network.key, "local"); - } - #[test] fn test_parse_networks_from_yaml() { let yaml = r#" diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index 34fbff04d..db085f534 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -5,7 +5,7 @@ pub mod orderbook; use crate::{ NetworkCfg, ParseDeployerConfigSourceError, ParseDeploymentConfigSourceError, - ParseNetworkConfigSourceError, ParseOrderConfigSourceError, ParseOrderbookConfigSourceError, + ParseNetworkCfgError, ParseOrderConfigSourceError, ParseOrderbookConfigSourceError, ParseScenarioCfgError, ParseTokenConfigSourceError, TokenCfg, }; use alloy::primitives::ruint::ParseError as RuintParseError; @@ -174,7 +174,7 @@ pub enum YamlError { InvalidTraitFunction, #[error(transparent)] - ParseNetworkConfigSourceError(#[from] ParseNetworkConfigSourceError), + ParseNetworkCfgError(#[from] ParseNetworkCfgError), #[error(transparent)] ParseTokenConfigSourceError(#[from] ParseTokenConfigSourceError), #[error(transparent)] @@ -219,9 +219,7 @@ impl PartialEq for YamlError { (Self::RuintParseError(e1), Self::RuintParseError(e2)) => { e1.to_string() == e2.to_string() } - (Self::ParseNetworkConfigSourceError(e1), Self::ParseNetworkConfigSourceError(e2)) => { - e1 == e2 - } + (Self::ParseNetworkCfgError(e1), Self::ParseNetworkCfgError(e2)) => e1 == e2, (Self::ParseTokenConfigSourceError(e1), Self::ParseTokenConfigSourceError(e2)) => { e1 == e2 } @@ -296,7 +294,7 @@ impl YamlError { YamlError::InvalidTraitFunction => { "There is an internal error in the YAML processing".to_string() } - YamlError::ParseNetworkConfigSourceError(err) => { + YamlError::ParseNetworkCfgError(err) => { format!("Network configuration error in your YAML: {}", err) } YamlError::ParseTokenConfigSourceError(err) => format!( From 9c896a90f3846cd24a1d6dd9b6d3002d85aba8b6 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:26:38 +0300 Subject: [PATCH 04/51] update remote config --- crates/settings/src/remote/chains/chainid.rs | 23 +------------------- crates/settings/src/remote/chains/mod.rs | 17 --------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/crates/settings/src/remote/chains/chainid.rs b/crates/settings/src/remote/chains/chainid.rs index 3b244ce4e..691848c5f 100644 --- a/crates/settings/src/remote/chains/chainid.rs +++ b/crates/settings/src/remote/chains/chainid.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, RwLock}; -use crate::{config_source::*, NetworkCfg}; +use crate::NetworkCfg; use alloy::primitives::Address; use serde::{Deserialize, Serialize}; use strict_yaml_rust::StrictYaml; @@ -65,27 +65,6 @@ pub enum ChainIdError { NoRpc, } -impl TryFrom for NetworkConfigSource { - type Error = ChainIdError; - fn try_from(value: ChainId) -> Result { - if value.rpc.is_empty() { - return Err(ChainIdError::NoRpc); - } - for rpc in &value.rpc { - if !rpc.path().contains("API_KEY") && !rpc.scheme().starts_with("ws") { - return Ok(NetworkConfigSource { - chain_id: value.chain_id, - rpc: rpc.clone(), - network_id: Some(value.network_id), - currency: Some(value.native_currency.symbol), - label: Some(value.name), - }); - } - } - Err(ChainIdError::UnsupportedRpcUrls) - } -} - impl ChainId { pub fn try_into_network_cfg( self, diff --git a/crates/settings/src/remote/chains/mod.rs b/crates/settings/src/remote/chains/mod.rs index 5c1ed66a6..556ecc120 100644 --- a/crates/settings/src/remote/chains/mod.rs +++ b/crates/settings/src/remote/chains/mod.rs @@ -1,4 +1,3 @@ -use crate::RemoteNetworksConfigSource; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -16,19 +15,3 @@ pub enum RemoteNetworkError { pub enum RemoteNetworks { ChainId(Vec), } - -impl RemoteNetworks { - pub async fn try_from_remote_network_config_source( - value: RemoteNetworksConfigSource, - ) -> Result { - match value.format.as_str() { - "chainid" => Ok(Self::ChainId( - reqwest::get(value.url) - .await? - .json::>() - .await?, - )), - _ => Err(RemoteNetworkError::UnknownFormat(value.format.clone())), - } - } -} From d400c65cb8a0ce03c7fff011fe5eb68edf0f4fa6 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:27:56 +0300 Subject: [PATCH 05/51] update token config --- crates/settings/src/token.rs | 128 +++----------------------------- crates/settings/src/yaml/mod.rs | 10 +-- 2 files changed, 16 insertions(+), 122 deletions(-) diff --git a/crates/settings/src/token.rs b/crates/settings/src/token.rs index 55753d5e8..4b12269d5 100644 --- a/crates/settings/src/token.rs +++ b/crates/settings/src/token.rs @@ -36,13 +36,13 @@ pub struct TokenCfg { impl_wasm_traits!(TokenCfg); impl TokenCfg { - pub fn validate_address(value: &str) -> Result { - Address::from_str(value).map_err(ParseTokenConfigSourceError::AddressParseError) + pub fn validate_address(value: &str) -> Result { + Address::from_str(value).map_err(ParseTokenCfgError::AddressParseError) } - pub fn validate_decimals(value: &str) -> Result { + pub fn validate_decimals(value: &str) -> Result { value .parse::() - .map_err(ParseTokenConfigSourceError::DecimalsParseError) + .map_err(ParseTokenCfgError::DecimalsParseError) } pub fn update_address(&mut self, address: &str) -> Result { @@ -304,8 +304,8 @@ impl YamlParsableHash for TokenCfg { if let Some(yaml_cache) = &context.yaml_cache { for (key, token) in &yaml_cache.remote_tokens { if tokens.contains_key(key) { - return Err(YamlError::ParseTokenConfigSourceError( - ParseTokenConfigSourceError::RemoteTokenKeyShadowing(key.clone()), + return Err(YamlError::ParseTokenCfgError( + ParseTokenCfgError::RemoteTokenKeyShadowing(key.clone()), )); } tokens.insert(key.clone(), token.clone()); @@ -349,7 +349,7 @@ impl PartialEq for TokenCfg { } #[derive(Error, Debug, PartialEq)] -pub enum ParseTokenConfigSourceError { +pub enum ParseTokenCfgError { #[error("Failed to parse address")] AddressParseError(FromHexError), #[error("Failed to parse decimals")] @@ -360,46 +360,21 @@ pub enum ParseTokenConfigSourceError { RemoteTokenKeyShadowing(String), } -impl ParseTokenConfigSourceError { +impl ParseTokenCfgError { pub fn to_readable_msg(&self) -> String { match self { - ParseTokenConfigSourceError::AddressParseError(err) => + ParseTokenCfgError::AddressParseError(err) => format!("The token address in your YAML configuration is invalid. Please provide a valid EVM address: {}", err), - ParseTokenConfigSourceError::DecimalsParseError(err) => + ParseTokenCfgError::DecimalsParseError(err) => format!("The token decimals in your YAML configuration must be a valid number between 0 and 255: {}", err), - ParseTokenConfigSourceError::NetworkNotFoundError(network) => + ParseTokenCfgError::NetworkNotFoundError(network) => format!("The network '{}' specified for this token was not found in your YAML configuration. Please define this network or use an existing one.", network), - ParseTokenConfigSourceError::RemoteTokenKeyShadowing(key) => + ParseTokenCfgError::RemoteTokenKeyShadowing(key) => format!("The remote token key '{}' is already defined in token configuration", key), } } } -impl TokenConfigSource { - pub fn try_into_token( - self, - name: &str, - networks: &HashMap>, - ) -> Result { - let network_ref = networks - .get(&self.network) - .ok_or(ParseTokenConfigSourceError::NetworkNotFoundError( - self.network.clone(), - )) - .map(Arc::clone)?; - - Ok(TokenCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: name.to_string(), - network: network_ref, - address: self.address, - decimals: self.decimals, - label: self.label, - symbol: self.symbol, - }) - } -} - #[cfg(test)] mod tests { use self::test::*; @@ -414,85 +389,6 @@ mod tests { networks } - #[test] - fn test_token_creation_success_with_all_fields() { - let networks = setup_networks(); - let token_string = TokenConfigSource { - network: "TestNetwork".to_string(), - address: Address::repeat_byte(0x01), - decimals: Some(18), - label: Some("TestToken".to_string()), - symbol: Some("TTK".to_string()), - }; - - let token = token_string.try_into_token("TestNetwork", &networks); - - assert!(token.is_ok()); - let token = token.unwrap(); - - assert_eq!(token.key, "TestNetwork"); - assert_eq!( - Arc::as_ptr(&token.network), - Arc::as_ptr(networks.get("TestNetwork").unwrap()) - ); - assert_eq!(token.address, Address::repeat_byte(0x01)); - assert_eq!(token.decimals, Some(18)); - assert_eq!(token.label, Some("TestToken".to_string())); - assert_eq!(token.symbol, Some("TTK".to_string())); - } - - #[test] - fn test_token_creation_success_with_minimal_fields() { - let networks = setup_networks(); - let token_string = TokenConfigSource { - network: "TestNetwork".to_string(), - address: Address::repeat_byte(0x01), - decimals: None, - label: None, - symbol: None, - }; - - let token = token_string.try_into_token("TestNetwork", &networks); - - assert!(token.is_ok()); - let token = token.unwrap(); - - assert_eq!(token.key, "TestNetwork"); - assert_eq!( - Arc::as_ptr(&token.network), - Arc::as_ptr(networks.get("TestNetwork").unwrap()) - ); - assert_eq!(token.address, Address::repeat_byte(0x01)); - assert_eq!(token.decimals, None); - assert_eq!(token.label, None); - assert_eq!(token.symbol, None); - } - - #[test] - fn test_token_creation_failure_due_to_invalid_network() { - let networks = setup_networks(); - let token_string = TokenConfigSource { - network: "InvalidNetwork".to_string(), - address: Address::repeat_byte(0x01), - decimals: None, - label: None, - symbol: None, - }; - - let token = token_string.try_into_token("TestNetwork", &networks); - - assert!(token.is_err()); - let error = token.unwrap_err(); - assert_eq!( - error, - ParseTokenConfigSourceError::NetworkNotFoundError("InvalidNetwork".to_string()) - ); - assert_eq!( - error.to_readable_msg(), - "The network 'InvalidNetwork' specified for this token was not found in your YAML configuration. Please define this network or use an existing one." - ); - } - #[test] fn test_parse_tokens_errors() { let error = TokenCfg::parse_all_from_yaml( diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index db085f534..e89aeaafe 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -6,7 +6,7 @@ pub mod orderbook; use crate::{ NetworkCfg, ParseDeployerConfigSourceError, ParseDeploymentConfigSourceError, ParseNetworkCfgError, ParseOrderConfigSourceError, ParseOrderbookConfigSourceError, - ParseScenarioCfgError, ParseTokenConfigSourceError, TokenCfg, + ParseScenarioCfgError, ParseTokenCfgError, TokenCfg, }; use alloy::primitives::ruint::ParseError as RuintParseError; use context::{Context, ContextError}; @@ -176,7 +176,7 @@ pub enum YamlError { #[error(transparent)] ParseNetworkCfgError(#[from] ParseNetworkCfgError), #[error(transparent)] - ParseTokenConfigSourceError(#[from] ParseTokenConfigSourceError), + ParseTokenCfgError(#[from] ParseTokenCfgError), #[error(transparent)] ParseOrderbookConfigSourceError(#[from] ParseOrderbookConfigSourceError), #[error(transparent)] @@ -220,9 +220,7 @@ impl PartialEq for YamlError { e1.to_string() == e2.to_string() } (Self::ParseNetworkCfgError(e1), Self::ParseNetworkCfgError(e2)) => e1 == e2, - (Self::ParseTokenConfigSourceError(e1), Self::ParseTokenConfigSourceError(e2)) => { - e1 == e2 - } + (Self::ParseTokenCfgError(e1), Self::ParseTokenCfgError(e2)) => e1 == e2, ( Self::ParseOrderbookConfigSourceError(e1), Self::ParseOrderbookConfigSourceError(e2), @@ -297,7 +295,7 @@ impl YamlError { YamlError::ParseNetworkCfgError(err) => { format!("Network configuration error in your YAML: {}", err) } - YamlError::ParseTokenConfigSourceError(err) => format!( + YamlError::ParseTokenCfgError(err) => format!( "Token configuration error in your YAML: {}", err.to_readable_msg() ), From c49c8c7d57b5d125153705cf8db0e5629b0f14d6 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:29:24 +0300 Subject: [PATCH 06/51] update orderbook config --- crates/settings/src/orderbook.rs | 146 ++----------------------------- crates/settings/src/yaml/mod.rs | 11 +-- 2 files changed, 11 insertions(+), 146 deletions(-) diff --git a/crates/settings/src/orderbook.rs b/crates/settings/src/orderbook.rs index 052f86f6b..131a04b94 100644 --- a/crates/settings/src/orderbook.rs +++ b/crates/settings/src/orderbook.rs @@ -34,8 +34,8 @@ pub struct OrderbookCfg { impl_wasm_traits!(OrderbookCfg); impl OrderbookCfg { - pub fn validate_address(address: &str) -> Result { - Address::from_str(address).map_err(ParseOrderbookConfigSourceError::AddressParseError) + pub fn validate_address(address: &str) -> Result { + Address::from_str(address).map_err(ParseOrderbookCfgError::AddressParseError) } pub fn parse_network_key( @@ -184,7 +184,7 @@ impl PartialEq for OrderbookCfg { } #[derive(Error, Debug, PartialEq)] -pub enum ParseOrderbookConfigSourceError { +pub enum ParseOrderbookCfgError { #[error("Failed to parse address")] AddressParseError(FromHexError), #[error("Network not found for Orderbook: {0}")] @@ -193,72 +193,23 @@ pub enum ParseOrderbookConfigSourceError { SubgraphNotFoundError(String), } -impl ParseOrderbookConfigSourceError { +impl ParseOrderbookCfgError { pub fn to_readable_msg(&self) -> String { match self { - ParseOrderbookConfigSourceError::AddressParseError(err) => + ParseOrderbookCfgError::AddressParseError(err) => format!("The orderbook address in your YAML configuration is invalid. Please provide a valid EVM address: {}", err), - ParseOrderbookConfigSourceError::NetworkNotFoundError(network) => + ParseOrderbookCfgError::NetworkNotFoundError(network) => format!("The network '{}' specified for this orderbook was not found in your YAML configuration. Please define this network or use an existing one.", network), - ParseOrderbookConfigSourceError::SubgraphNotFoundError(subgraph) => + ParseOrderbookCfgError::SubgraphNotFoundError(subgraph) => format!("The subgraph '{}' specified for this orderbook was not found in your YAML configuration. Please define this subgraph or use an existing one.", subgraph), } } } -impl OrderbookConfigSource { - pub fn try_into_orderbook( - self, - name: String, - networks: &HashMap>, - subgraphs: &HashMap>, - ) -> Result { - let network_ref = match self.network { - Some(network_name) => networks - .get(&network_name) - .ok_or(ParseOrderbookConfigSourceError::NetworkNotFoundError( - network_name.clone(), - )) - .map(Arc::clone)?, - None => networks - .get(&name) - .ok_or(ParseOrderbookConfigSourceError::NetworkNotFoundError( - name.clone(), - )) - .map(Arc::clone)?, - }; - - let subgraph_ref = match self.subgraph { - Some(subgraph_name) => subgraphs - .get(&subgraph_name) - .ok_or(ParseOrderbookConfigSourceError::SubgraphNotFoundError( - subgraph_name.clone(), - )) - .map(Arc::clone)?, - None => subgraphs - .get(&name) - .ok_or(ParseOrderbookConfigSourceError::SubgraphNotFoundError( - name.clone(), - )) - .map(Arc::clone)?, - }; - - Ok(OrderbookCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: name, - address: self.address, - network: network_ref, - subgraph: subgraph_ref, - label: self.label, - }) - } -} - #[cfg(test)] mod tests { use super::*; use crate::test::*; - use alloy::primitives::Address; use strict_yaml_rust::StrictYamlLoader; fn setup() -> ( @@ -277,89 +228,6 @@ mod tests { (networks, subgraphs) } - #[test] - fn test_orderbook_creation_success() { - let (networks, subgraphs) = setup(); - let address = "0x1234567890123456789012345678901234567890" - .parse::
() - .unwrap(); - let orderbook_string = OrderbookConfigSource { - address, - network: Some("TestNetwork".to_string()), - subgraph: Some("TestSubgraph".to_string()), - label: Some("TestLabel".to_string()), - }; - - let orderbook = - orderbook_string.try_into_orderbook("TestName".to_string(), &networks, &subgraphs); - - assert!(orderbook.is_ok()); - let orderbook = orderbook.unwrap(); - - assert_eq!(orderbook.address, address); - assert_eq!( - Arc::as_ptr(&orderbook.network), - Arc::as_ptr(networks.get("TestNetwork").unwrap()) - ); - assert_eq!( - Arc::as_ptr(&orderbook.subgraph), - Arc::as_ptr(subgraphs.get("TestSubgraph").unwrap()) - ); - assert_eq!(orderbook.label, Some("TestLabel".to_string())); - } - - #[test] - fn test_orderbook_creation_with_missing_network() { - let (networks, subgraphs) = setup(); - let orderbook_string = OrderbookConfigSource { - address: Address::random(), - network: Some("NonExistingNetwork".to_string()), - subgraph: Some("TestSubgraph".to_string()), - label: None, - }; - - let result = - orderbook_string.try_into_orderbook("TestName".to_string(), &networks, &subgraphs); - - assert!(result.is_err()); - let error = result.unwrap_err(); - assert_eq!( - error, - ParseOrderbookConfigSourceError::NetworkNotFoundError("NonExistingNetwork".to_string()) - ); - assert_eq!( - error.to_readable_msg(), - "The network 'NonExistingNetwork' specified for this orderbook was not found in your YAML configuration. Please define this network or use an existing one." - ); - } - - #[test] - fn test_orderbook_creation_with_missing_subgraph() { - let (networks, subgraphs) = setup(); - let orderbook_string = OrderbookConfigSource { - address: Address::random(), - network: Some("TestNetwork".to_string()), - subgraph: Some("NonExistingSubgraph".to_string()), - label: None, - }; - - let result = - orderbook_string.try_into_orderbook("TestName".to_string(), &networks, &subgraphs); - - assert!(result.is_err()); - let error = result.unwrap_err(); - assert_eq!( - error, - ParseOrderbookConfigSourceError::SubgraphNotFoundError( - "NonExistingSubgraph".to_string() - ) - ); - assert_eq!( - error.to_readable_msg(), - "The subgraph 'NonExistingSubgraph' specified for this orderbook was not found in your YAML configuration. Please define this subgraph or use an existing one." - ); - } - fn get_document(yaml: &str) -> Arc> { let document = StrictYamlLoader::load_from_str(yaml).unwrap()[0].clone(); Arc::new(RwLock::new(document)) diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index e89aeaafe..1b7ecf446 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -5,7 +5,7 @@ pub mod orderbook; use crate::{ NetworkCfg, ParseDeployerConfigSourceError, ParseDeploymentConfigSourceError, - ParseNetworkCfgError, ParseOrderConfigSourceError, ParseOrderbookConfigSourceError, + ParseNetworkCfgError, ParseOrderConfigSourceError, ParseOrderbookCfgError, ParseScenarioCfgError, ParseTokenCfgError, TokenCfg, }; use alloy::primitives::ruint::ParseError as RuintParseError; @@ -178,7 +178,7 @@ pub enum YamlError { #[error(transparent)] ParseTokenCfgError(#[from] ParseTokenCfgError), #[error(transparent)] - ParseOrderbookConfigSourceError(#[from] ParseOrderbookConfigSourceError), + ParseOrderbookCfgError(#[from] ParseOrderbookCfgError), #[error(transparent)] ParseDeployerConfigSourceError(#[from] ParseDeployerConfigSourceError), #[error(transparent)] @@ -221,10 +221,7 @@ impl PartialEq for YamlError { } (Self::ParseNetworkCfgError(e1), Self::ParseNetworkCfgError(e2)) => e1 == e2, (Self::ParseTokenCfgError(e1), Self::ParseTokenCfgError(e2)) => e1 == e2, - ( - Self::ParseOrderbookConfigSourceError(e1), - Self::ParseOrderbookConfigSourceError(e2), - ) => e1 == e2, + (Self::ParseOrderbookCfgError(e1), Self::ParseOrderbookCfgError(e2)) => e1 == e2, ( Self::ParseDeployerConfigSourceError(e1), Self::ParseDeployerConfigSourceError(e2), @@ -299,7 +296,7 @@ impl YamlError { "Token configuration error in your YAML: {}", err.to_readable_msg() ), - YamlError::ParseOrderbookConfigSourceError(err) => format!( + YamlError::ParseOrderbookCfgError(err) => format!( "Orderbook configuration error in your YAML: {}", err.to_readable_msg() ), From 0dd58043a66f090a0c9d71d876000f0dc1104a96 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:33:44 +0300 Subject: [PATCH 07/51] update chart config --- crates/settings/src/chart.rs | 204 +---------------------------------- 1 file changed, 1 insertion(+), 203 deletions(-) diff --git a/crates/settings/src/chart.rs b/crates/settings/src/chart.rs index a29e1a191..07dd99f96 100644 --- a/crates/settings/src/chart.rs +++ b/crates/settings/src/chart.rs @@ -654,212 +654,10 @@ pub enum ParseChartConfigSourceError { ScenarioNotFoundError(String), } -impl ChartConfigSource { - pub fn try_into_chart( - self, - name: String, - scenarios: &HashMap>, - ) -> Result { - let scenario_ref = match self.scenario { - Some(scenario_name) => scenarios - .get(&scenario_name) - .ok_or(ParseChartConfigSourceError::ScenarioNotFoundError( - scenario_name.clone(), - )) - .map(Arc::clone)?, - None => scenarios - .get(&name) - .ok_or(ParseChartConfigSourceError::ScenarioNotFoundError( - name.clone(), - )) - .map(Arc::clone)?, - }; - - // Convert `self.plots` from Option> to Option> - let plots = self.plots.map(|plots_map| { - plots_map - .into_iter() - .map(|(name, mut plot)| { - // If the plot does not have a title, use the name from the map - plot.title.get_or_insert(name); - plot - }) - .collect::>() - }); - - Ok(ChartCfg { - document: default_document(), - key: name, - scenario: scenario_ref, - metrics: self.metrics, - plots, - }) - } -} - #[cfg(test)] mod tests { - use strict_yaml_rust::StrictYaml; - - use crate::test::mock_plot; - use crate::yaml::tests::get_document; - - use self::test::mock_deployer; - use super::*; - use std::collections::HashMap; - use std::sync::{Arc, RwLock}; - - fn create_scenario(name: &str, runs: Option) -> (String, Arc) { - let scenario = ScenarioCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: name.into(), - bindings: HashMap::from([(String::from("key"), String::from("value"))]), // Example binding - runs, - blocks: None, - deployer: mock_deployer(), - }; - (name.to_string(), Arc::new(scenario)) - } - - #[test] - fn test_success_explicit_scenario_name() { - let (scenario_name, scenario) = create_scenario("scenario1", 100.into()); - let mut scenarios = HashMap::new(); - scenarios.insert(scenario_name.clone(), scenario); - - let mut plots = HashMap::new(); - let (plot_name, plot) = mock_plot("plot1"); - plots.insert(plot_name, plot); - - let chart_string = ChartConfigSource { - scenario: Some(scenario_name), - plots: Some(plots), - metrics: None, - }; - - let chart = chart_string - .try_into_chart("chart1".to_string(), &scenarios) - .unwrap(); - assert!(Arc::ptr_eq( - &chart.scenario, - scenarios.get("scenario1").unwrap() - )); - } - - #[test] - fn test_success_using_chart_name() { - let (chart_name, scenario) = create_scenario("chart2", 100.into()); - let mut scenarios = HashMap::new(); - scenarios.insert(chart_name.clone(), scenario); - - let mut plots = HashMap::new(); - let (plot_name, plot) = mock_plot("plot1"); - plots.insert(plot_name, plot); - - let chart_string = ChartConfigSource { - scenario: None, - plots: Some(plots), - metrics: None, - }; - - let chart = chart_string - .try_into_chart(chart_name.clone(), &scenarios) - .unwrap(); - assert!(Arc::ptr_eq( - &chart.scenario, - scenarios.get(&chart_name).unwrap() - )); - } - - #[test] - fn test_scenario_not_found_error() { - let scenarios = HashMap::>::new(); // No scenarios added - - let mut plots = HashMap::new(); - let (plot_name, plot) = mock_plot("plot1"); - plots.insert(plot_name, plot); - - let chart_string = ChartConfigSource { - scenario: Some("nonexistent_scenario".to_string()), - plots: Some(plots), - metrics: None, - }; - - let result = chart_string.try_into_chart("chart3".to_string(), &scenarios); - assert!(matches!( - result, - Err(ParseChartConfigSourceError::ScenarioNotFoundError(_)) - )); - } - - #[test] - fn test_no_scenario_matching_chart_name() { - let scenarios = HashMap::>::new(); // No scenarios added - - let chart_string = ChartConfigSource { - scenario: None, - plots: None, - metrics: None, - }; - - let result = chart_string.try_into_chart("chart4".to_string(), &scenarios); - assert!(matches!( - result, - Err(ParseChartConfigSourceError::ScenarioNotFoundError(_)) - )); - } - - #[test] - fn test_multiple_plots() { - let (scenario_name, scenario) = create_scenario("scenario5", 200.into()); - let mut scenarios = HashMap::new(); - scenarios.insert(scenario_name.clone(), scenario); - - let mut plots = HashMap::new(); - let (plot_name, plot) = mock_plot("plot1"); - plots.insert(plot_name, plot); - - let (plot_name, plot) = mock_plot("plot2"); - plots.insert(plot_name, plot); - - let metrics: Vec = vec![MetricCfg { - label: "label".to_string(), - description: Some("description".to_string()), - unit_prefix: Some("unit_prefix".to_string()), - unit_suffix: Some("unit_suffix".to_string()), - value: "value".to_string(), - precision: Some(2), - }]; - - let chart_string = ChartConfigSource { - scenario: Some(scenario_name), - plots: Some(plots), - metrics: Some(metrics), - }; - - let chart = chart_string - .try_into_chart("chart5".to_string(), &scenarios) - .unwrap(); - assert!(Arc::ptr_eq( - &chart.scenario, - scenarios.get("scenario5").unwrap() - )); - assert_eq!(chart.clone().plots.unwrap().len(), 2); - - // both plots should have the name "Title" - let mut plots = chart - .plots - .unwrap() - .iter() - .map(|p| p.title.clone()) - .collect::>>(); - plots.sort(); - assert_eq!( - plots, - vec![Some("Title".to_string()), Some("Title".to_string())] - ); - } + use crate::yaml::tests::get_document; const PREFIX: &str = r#" networks: From 164236dce5cfa7d040afb13a5730f72ea50e5cb8 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:38:58 +0300 Subject: [PATCH 08/51] update deployer config --- crates/settings/src/chart.rs | 6 -- crates/settings/src/deployer.rs | 112 ++------------------------------ crates/settings/src/yaml/mod.rs | 15 ++--- 3 files changed, 12 insertions(+), 121 deletions(-) diff --git a/crates/settings/src/chart.rs b/crates/settings/src/chart.rs index 07dd99f96..f4dfe0f5e 100644 --- a/crates/settings/src/chart.rs +++ b/crates/settings/src/chart.rs @@ -648,12 +648,6 @@ impl PartialEq for ChartCfg { } } -#[derive(Error, Debug, PartialEq)] -pub enum ParseChartConfigSourceError { - #[error("Scenario not found: {0}")] - ScenarioNotFoundError(String), -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/settings/src/deployer.rs b/crates/settings/src/deployer.rs index 034bfc2c7..f827d0fca 100644 --- a/crates/settings/src/deployer.rs +++ b/crates/settings/src/deployer.rs @@ -39,8 +39,8 @@ impl DeployerCfg { } } - pub fn validate_address(value: &str) -> Result { - Address::from_str(value).map_err(ParseDeployerConfigSourceError::AddressParseError) + pub fn validate_address(value: &str) -> Result { + Address::from_str(value).map_err(ParseDeployerCfgError::AddressParseError) } pub fn parse_network_key( @@ -87,54 +87,24 @@ impl PartialEq for DeployerCfg { } #[derive(Error, Debug, PartialEq)] -pub enum ParseDeployerConfigSourceError { +pub enum ParseDeployerCfgError { #[error("Failed to parse address")] AddressParseError(alloy::primitives::hex::FromHexError), #[error("Network not found for Deployer: {0}")] NetworkNotFoundError(String), } -impl ParseDeployerConfigSourceError { +impl ParseDeployerCfgError { pub fn to_readable_msg(&self) -> String { match self { - ParseDeployerConfigSourceError::AddressParseError(err) => + ParseDeployerCfgError::AddressParseError(err) => format!("The deployer address in your YAML configuration is invalid. Please provide a valid EVM address: {}", err), - ParseDeployerConfigSourceError::NetworkNotFoundError(network) => + ParseDeployerCfgError::NetworkNotFoundError(network) => format!("The network '{}' specified for this deployer was not found in your YAML configuration. Please define this network or use an existing one.", network), } } } -impl DeployerConfigSource { - pub fn try_into_deployer( - self, - name: String, - networks: &HashMap>, - ) -> Result { - let network_ref = match self.network { - Some(network_name) => networks - .get(&network_name) - .ok_or(ParseDeployerConfigSourceError::NetworkNotFoundError( - network_name.clone(), - )) - .map(Arc::clone)?, - None => networks - .get(&name) - .ok_or(ParseDeployerConfigSourceError::NetworkNotFoundError( - name.clone(), - )) - .map(Arc::clone)?, - }; - - Ok(DeployerCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: name, - address: self.address, - network: network_ref, - }) - } -} - impl YamlParsableHash for DeployerCfg { fn parse_all_from_yaml( documents: Vec>>, @@ -210,78 +180,8 @@ impl YamlParsableHash for DeployerCfg { #[cfg(test)] mod tests { use super::*; - use crate::test::*; use crate::yaml::tests::get_document; - #[test] - fn test_try_into_deployer_success() { - let address = Address::repeat_byte(0x01); // Generate a random address for testing - let network_name = "Local Testnet"; - let networks = HashMap::from([(network_name.to_string(), mock_network())]); - let deployer_string = DeployerConfigSource { - address, - network: Some(network_name.to_string()), - label: Some("Test Deployer".to_string()), - }; - - let result = deployer_string.try_into_deployer(network_name.to_string(), &networks); - assert!(result.is_ok()); - let deployer = result.unwrap(); - assert_eq!(deployer.address, address); - assert_eq!( - deployer.network.as_ref().label, - Some(network_name.to_string()) - ); - } - - #[test] - fn test_try_into_deployer_network_not_found_error() { - let address = Address::repeat_byte(0x01); - let invalid_network_name = "unknownnet"; - let networks = HashMap::new(); // Empty networks map - let deployer_string = DeployerConfigSource { - address, - network: Some(invalid_network_name.to_string()), - label: None, - }; - - let result = deployer_string.try_into_deployer(invalid_network_name.to_string(), &networks); - assert!(matches!( - result, - Err(ParseDeployerConfigSourceError::NetworkNotFoundError(_)) - )); - let error = result.unwrap_err(); - assert_eq!( - error, - ParseDeployerConfigSourceError::NetworkNotFoundError(invalid_network_name.to_string()) - ); - assert_eq!( - error.to_readable_msg(), - "The network 'unknownnet' specified for this deployer was not found in your YAML configuration. Please define this network or use an existing one." - ); - } - - #[test] - fn test_try_into_deployer_no_network_specified() { - let address = Address::repeat_byte(0x01); - let network_name = "Local Testnet"; - let networks = HashMap::from([(network_name.to_string(), mock_network())]); - let deployer_string = DeployerConfigSource { - address, - network: None, // No network specified - label: None, - }; - - // Expecting to use the network name as provided in the name parameter of try_into_deployer - let result = deployer_string.try_into_deployer(network_name.to_string(), &networks); - assert!(result.is_ok()); - let deployer = result.unwrap(); - assert_eq!( - deployer.network.as_ref().label, - Some(network_name.to_string()) - ); - } - #[test] fn test_parse_deployers_from_yaml_multiple_files() { let yaml_one = r#" diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index 1b7ecf446..c8036e1bc 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -4,9 +4,9 @@ pub mod dotrain; pub mod orderbook; use crate::{ - NetworkCfg, ParseDeployerConfigSourceError, ParseDeploymentConfigSourceError, - ParseNetworkCfgError, ParseOrderConfigSourceError, ParseOrderbookCfgError, - ParseScenarioCfgError, ParseTokenCfgError, TokenCfg, + NetworkCfg, ParseDeployerCfgError, ParseDeploymentConfigSourceError, ParseNetworkCfgError, + ParseOrderConfigSourceError, ParseOrderbookCfgError, ParseScenarioCfgError, ParseTokenCfgError, + TokenCfg, }; use alloy::primitives::ruint::ParseError as RuintParseError; use context::{Context, ContextError}; @@ -180,7 +180,7 @@ pub enum YamlError { #[error(transparent)] ParseOrderbookCfgError(#[from] ParseOrderbookCfgError), #[error(transparent)] - ParseDeployerConfigSourceError(#[from] ParseDeployerConfigSourceError), + ParseDeployerCfgError(#[from] ParseDeployerCfgError), #[error(transparent)] ParseOrderConfigSourceError(#[from] ParseOrderConfigSourceError), #[error(transparent)] @@ -222,10 +222,7 @@ impl PartialEq for YamlError { (Self::ParseNetworkCfgError(e1), Self::ParseNetworkCfgError(e2)) => e1 == e2, (Self::ParseTokenCfgError(e1), Self::ParseTokenCfgError(e2)) => e1 == e2, (Self::ParseOrderbookCfgError(e1), Self::ParseOrderbookCfgError(e2)) => e1 == e2, - ( - Self::ParseDeployerConfigSourceError(e1), - Self::ParseDeployerConfigSourceError(e2), - ) => e1 == e2, + (Self::ParseDeployerCfgError(e1), Self::ParseDeployerCfgError(e2)) => e1 == e2, (Self::ParseOrderConfigSourceError(e1), Self::ParseOrderConfigSourceError(e2)) => { e1 == e2 } @@ -300,7 +297,7 @@ impl YamlError { "Orderbook configuration error in your YAML: {}", err.to_readable_msg() ), - YamlError::ParseDeployerConfigSourceError(err) => format!( + YamlError::ParseDeployerCfgError(err) => format!( "Deployer configuration error in your YAML: {}", err.to_readable_msg() ), From 1e62ebbeac819378618860fcde6a148faad6fdf8 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:40:06 +0300 Subject: [PATCH 09/51] update deployment config --- crates/settings/src/deployment.rs | 122 ++---------------------------- crates/settings/src/yaml/mod.rs | 11 +-- 2 files changed, 12 insertions(+), 121 deletions(-) diff --git a/crates/settings/src/deployment.rs b/crates/settings/src/deployment.rs index a40b5efa0..e6869c184 100644 --- a/crates/settings/src/deployment.rs +++ b/crates/settings/src/deployment.rs @@ -100,8 +100,8 @@ impl YamlParsableHash for DeploymentCfg { if let Some(deployer) = &order.deployer { if deployer != &scenario.deployer { - return Err(YamlError::ParseDeploymentConfigSourceError( - ParseDeploymentConfigSourceError::NoMatch, + return Err(YamlError::ParseDeploymentCfgError( + ParseDeploymentCfgError::NoMatch, )); } } @@ -163,7 +163,7 @@ impl PartialEq for DeploymentCfg { } #[derive(Error, Debug, PartialEq)] -pub enum ParseDeploymentConfigSourceError { +pub enum ParseDeploymentCfgError { #[error("Scenario not found: {0}")] ScenarioNotFoundError(String), #[error("Order not found: {0}")] @@ -172,129 +172,24 @@ pub enum ParseDeploymentConfigSourceError { NoMatch, } -impl ParseDeploymentConfigSourceError { +impl ParseDeploymentCfgError { pub fn to_readable_msg(&self) -> String { match self { - ParseDeploymentConfigSourceError::ScenarioNotFoundError(scenario) => + ParseDeploymentCfgError::ScenarioNotFoundError(scenario) => format!("The scenario '{}' referenced in your deployment configuration was not found in your YAML configuration. Please check that this scenario is defined correctly.", scenario), - ParseDeploymentConfigSourceError::OrderNotFoundError(order) => + ParseDeploymentCfgError::OrderNotFoundError(order) => format!("The order '{}' referenced in your deployment configuration was not found in your YAML configuration. Please check that this order is defined correctly.", order), - ParseDeploymentConfigSourceError::NoMatch => + ParseDeploymentCfgError::NoMatch => "The scenario and order in your deployment configuration do not match. The deployer specified in the order must match the deployer specified in the scenario.".to_string(), } } } -impl DeploymentConfigSource { - pub fn try_into_deployment( - self, - scenarios: &HashMap>, - orders: &HashMap>, - ) -> Result { - let scenario = scenarios - .get(&self.scenario) - .ok_or(ParseDeploymentConfigSourceError::ScenarioNotFoundError( - self.scenario.clone(), - )) - .map(Arc::clone)?; - - let order = orders - .get(&self.order) - .ok_or(ParseDeploymentConfigSourceError::OrderNotFoundError( - self.order.clone(), - )) - .map(Arc::clone)?; - - // check validity - if let Some(deployer) = &order.deployer { - if deployer != &scenario.deployer { - return Err(ParseDeploymentConfigSourceError::NoMatch); - } - }; - - Ok(DeploymentCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: scenario.key.clone(), - scenario, - order, - }) - } -} - #[cfg(test)] mod tests { use super::*; - use crate::test::*; - use std::sync::RwLock; - use strict_yaml_rust::StrictYaml; use yaml::tests::get_document; - #[test] - fn test_try_into_deployment_success() { - let order_name = "order1"; - let scenario_name = "scenario1"; - let scenario = ScenarioCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: "scenario1".into(), - bindings: HashMap::new(), - deployer: mock_deployer(), - runs: None, - blocks: None, - }; - let order = OrderCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: String::new(), - inputs: vec![], - outputs: vec![], - network: mock_network(), - deployer: None, - orderbook: None, - }; - let orders = HashMap::from([(order_name.to_string(), Arc::new(order))]); - let scenarios = HashMap::from([(scenario_name.to_string(), Arc::new(scenario))]); - let deploment_string = DeploymentConfigSource { - scenario: scenario_name.to_string(), - order: order_name.to_string(), - }; - let result = deploment_string.try_into_deployment(&scenarios, &orders); - assert!(result.is_ok()); - } - - #[test] - fn test_try_into_deployment_error() { - let order_name = "order1"; - let scenario_name = "scenario1"; - let other_scenario_name = "scenario2"; - let scenario = ScenarioCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: "scenario1".into(), - bindings: HashMap::new(), - deployer: mock_deployer(), - runs: None, - blocks: None, - }; - let order = OrderCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: String::new(), - inputs: vec![], - outputs: vec![], - network: mock_network(), - deployer: None, - orderbook: None, - }; - let orders = HashMap::from([(order_name.to_string(), Arc::new(order))]); - let scenarios = HashMap::from([(scenario_name.to_string(), Arc::new(scenario))]); - let deploment_string = DeploymentConfigSource { - scenario: other_scenario_name.to_string(), - order: order_name.to_string(), - }; - let result = deploment_string.try_into_deployment(&scenarios, &orders); - assert!(matches!( - result, - Err(ParseDeploymentConfigSourceError::ScenarioNotFoundError(_)) - )); - } - #[test] fn test_parse_deployments_from_yaml() { let yaml = r#" @@ -446,8 +341,7 @@ deployments: let error = DeploymentCfg::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error.to_string(), - YamlError::ParseDeploymentConfigSourceError(ParseDeploymentConfigSourceError::NoMatch) - .to_string() + YamlError::ParseDeploymentCfgError(ParseDeploymentCfgError::NoMatch).to_string() ); assert_eq!( error.to_readable_msg(), diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index c8036e1bc..091365d40 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -4,7 +4,7 @@ pub mod dotrain; pub mod orderbook; use crate::{ - NetworkCfg, ParseDeployerCfgError, ParseDeploymentConfigSourceError, ParseNetworkCfgError, + NetworkCfg, ParseDeployerCfgError, ParseDeploymentCfgError, ParseNetworkCfgError, ParseOrderConfigSourceError, ParseOrderbookCfgError, ParseScenarioCfgError, ParseTokenCfgError, TokenCfg, }; @@ -186,7 +186,7 @@ pub enum YamlError { #[error(transparent)] ParseScenarioCfgError(#[from] ParseScenarioCfgError), #[error(transparent)] - ParseDeploymentConfigSourceError(#[from] ParseDeploymentConfigSourceError), + ParseDeploymentCfgError(#[from] ParseDeploymentCfgError), #[error(transparent)] ContextError(#[from] ContextError), } @@ -227,10 +227,7 @@ impl PartialEq for YamlError { e1 == e2 } (Self::ParseScenarioCfgError(e1), Self::ParseScenarioCfgError(e2)) => e1 == e2, - ( - Self::ParseDeploymentConfigSourceError(e1), - Self::ParseDeploymentConfigSourceError(e2), - ) => e1 == e2, + (Self::ParseDeploymentCfgError(e1), Self::ParseDeploymentCfgError(e2)) => e1 == e2, (Self::ContextError(e1), Self::ContextError(e2)) => e1.to_string() == e2.to_string(), _ => false, } @@ -309,7 +306,7 @@ impl YamlError { "Scenario configuration error in your YAML: {}", err.to_readable_msg() ), - YamlError::ParseDeploymentConfigSourceError(err) => format!( + YamlError::ParseDeploymentCfgError(err) => format!( "Deployment configuration error in your YAML: {}", err.to_readable_msg() ), From 2f815a14f225ad5a40718c39839724055eba4fd3 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:42:56 +0300 Subject: [PATCH 10/51] update order config --- crates/settings/src/order.rs | 576 ++++++++------------------------ crates/settings/src/yaml/mod.rs | 10 +- 2 files changed, 150 insertions(+), 436 deletions(-) diff --git a/crates/settings/src/order.rs b/crates/settings/src/order.rs index ab68d1b50..26e449428 100644 --- a/crates/settings/src/order.rs +++ b/crates/settings/src/order.rs @@ -50,8 +50,8 @@ pub struct OrderCfg { impl_wasm_traits!(OrderCfg); impl OrderCfg { - pub fn validate_vault_id(value: &str) -> Result { - U256::from_str(value).map_err(ParseOrderConfigSourceError::VaultParseError) + pub fn validate_vault_id(value: &str) -> Result { + U256::from_str(value).map_err(ParseOrderCfgError::VaultParseError) } pub fn update_vault_id( @@ -295,8 +295,8 @@ impl OrderCfg { if let Some(ref existing_key) = network_key { if *existing_key != key { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::DeployerNetworkDoesNotMatch { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::DeployerNetworkDoesNotMatch { expected: existing_key.clone(), found: key.clone(), }, @@ -313,8 +313,8 @@ impl OrderCfg { if let Some(ref existing_key) = network_key { if *existing_key != key { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::OrderbookNetworkDoesNotMatch { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::OrderbookNetworkDoesNotMatch { expected: existing_key.clone(), found: key.clone(), }, @@ -337,8 +337,8 @@ impl OrderCfg { if let Ok(key) = res { if let Some(ref existing_key) = network_key { if *existing_key != key { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::InputTokenNetworkDoesNotMatch { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::InputTokenNetworkDoesNotMatch { key: token_key, expected: existing_key.clone(), found: key.clone(), @@ -364,8 +364,8 @@ impl OrderCfg { if let Ok(key) = res { if let Some(ref existing_key) = network_key { if *existing_key != key { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::OutputTokenNetworkDoesNotMatch { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::OutputTokenNetworkDoesNotMatch { key: token_key, expected: existing_key.clone(), found: key.clone(), @@ -387,11 +387,7 @@ impl OrderCfg { } } - Ok( - network_key.ok_or(ParseOrderConfigSourceError::NetworkNotFoundError( - String::new(), - ))?, - ) + Ok(network_key.ok_or(ParseOrderCfgError::NetworkNotFoundError(String::new()))?) } pub fn parse_vault_ids( @@ -525,8 +521,8 @@ impl YamlParsableHash for OrderCfg { ); if let Some(n) = &network { if deployer.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::DeployerNetworkDoesNotMatch { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::DeployerNetworkDoesNotMatch { expected: n.key.clone(), found: deployer.network.key.clone(), }, @@ -559,8 +555,8 @@ impl YamlParsableHash for OrderCfg { ); if let Some(n) = &network { if orderbook.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::OrderbookNetworkDoesNotMatch { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::OrderbookNetworkDoesNotMatch { expected: n.key.clone(), found: orderbook.network.key.clone(), }, @@ -574,43 +570,49 @@ impl YamlParsableHash for OrderCfg { None => None, }; - let inputs = require_vec( - order_yaml, - "inputs", - Some(location.clone()), - )? - .iter() - .enumerate() - .map(|(i, input)| { - let location = format!("input index '{i}' in order '{order_key}'"); - - let token_name = require_string( - input, - Some("token"), - Some(location.clone()), - )?; - - let mut order_token = None; - - if let Ok(tokens) = &tokens { - let token = tokens.get(&token_name); - - if let Some(token) = token { - if let Some(n) = &network { - if token.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::InputTokenNetworkDoesNotMatch { - key: token_name, - expected: n.key.clone(), - found: token.network.key.clone(), + let inputs = require_vec(order_yaml, "inputs", Some(location.clone()))? + .iter() + .enumerate() + .map(|(i, input)| { + let location = format!("input index '{i}' in order '{order_key}'"); + + let token_name = + require_string(input, Some("token"), Some(location.clone()))?; + + let mut order_token = None; + + if let Ok(tokens) = &tokens { + let token = tokens.get(&token_name); + + if let Some(token) = token { + if let Some(n) = &network { + if token.network != *n { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::InputTokenNetworkDoesNotMatch { + key: token_name, + expected: n.key.clone(), + found: token.network.key.clone(), + }, + )); + } + } else { + network = Some(token.network.clone()); + } + + order_token = Some(token.clone()); + } else if let Some(context) = context { + if !context.is_select_token(&token_name) { + return Err(YamlError::Field { + kind: FieldErrorKind::InvalidValue { + field: "token".to_string(), + reason: format!( + "missing yaml data for token '{token_name}'" + ), }, - )); + location: location.clone(), + }); } - } else { - network = Some(token.network.clone()); } - - order_token = Some(token.clone()); } else if let Some(context) = context { if !context.is_select_token(&token_name) { return Err(YamlError::Field { @@ -624,77 +626,72 @@ impl YamlParsableHash for OrderCfg { }); } } - } else if let Some(context) = context { - if !context.is_select_token(&token_name) { - return Err(YamlError::Field { - kind: FieldErrorKind::InvalidValue { - field: "token".to_string(), - reason: format!( - "missing yaml data for token '{token_name}'" - ), - }, - location: location.clone(), - }); - } - } - let vault_id = match optional_string(input, "vault-id") { - Some(id) => Some(OrderCfg::validate_vault_id(&id).map_err(|e| { - YamlError::Field { - kind: FieldErrorKind::InvalidValue { - field: "vault-id".to_string(), - reason: e.to_string(), - }, - location: location.clone(), + let vault_id = match optional_string(input, "vault-id") { + Some(id) => { + Some(OrderCfg::validate_vault_id(&id).map_err(|e| { + YamlError::Field { + kind: FieldErrorKind::InvalidValue { + field: "vault-id".to_string(), + reason: e.to_string(), + }, + location: location.clone(), + } + })?) } - })?), - None => None, - }; + None => None, + }; - Ok(OrderIOCfg { - token: order_token.map(Arc::new), - vault_id, + Ok(OrderIOCfg { + token: order_token.map(Arc::new), + vault_id, + }) }) - }) - .collect::, YamlError>>()?; - - let outputs = require_vec( - order_yaml, - "outputs", - Some(location.clone()), - )? - .iter() - .enumerate() - .map(|(i, output)| { - let location = format!("output index '{i}' in order '{order_key}'"); - - let token_name = require_string( - output, - Some("token"), - Some(location.clone()), - )?; - - let mut order_token = None; - - if let Ok(tokens) = &tokens { - let token = tokens.get(&token_name); - - if let Some(token) = token { - if let Some(n) = &network { - if token.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::OutputTokenNetworkDoesNotMatch { + .collect::, YamlError>>()?; + + let outputs = require_vec(order_yaml, "outputs", Some(location.clone()))? + .iter() + .enumerate() + .map(|(i, output)| { + let location = format!("output index '{i}' in order '{order_key}'"); + + let token_name = + require_string(output, Some("token"), Some(location.clone()))?; + + let mut order_token = None; + + if let Ok(tokens) = &tokens { + let token = tokens.get(&token_name); + + if let Some(token) = token { + if let Some(n) = &network { + if token.network != *n { + return Err(YamlError::ParseOrderCfgError( + ParseOrderCfgError::OutputTokenNetworkDoesNotMatch { key: token_name, expected: n.key.clone(), found: token.network.key.clone(), }, )); + } + } else { + network = Some(token.network.clone()); } - } else { - network = Some(token.network.clone()); - } - order_token = Some(token.clone()); + order_token = Some(token.clone()); + } else if let Some(context) = context { + if !context.is_select_token(&token_name) { + return Err(YamlError::Field { + kind: FieldErrorKind::InvalidValue { + field: "token".to_string(), + reason: format!( + "missing yaml data for token '{token_name}'" + ), + }, + location: location.clone(), + }); + } + } } else if let Some(context) = context { if !context.is_select_token(&token_name) { return Err(YamlError::Field { @@ -708,48 +705,36 @@ impl YamlParsableHash for OrderCfg { }); } } - } else if let Some(context) = context { - if !context.is_select_token(&token_name) { - return Err(YamlError::Field { - kind: FieldErrorKind::InvalidValue { - field: "token".to_string(), - reason: format!( - "missing yaml data for token '{token_name}'" - ), - }, - location: location.clone(), - }); - } - } - let vault_id = match optional_string(output, "vault-id") { - Some(id) => Some(OrderCfg::validate_vault_id(&id).map_err(|e| { - YamlError::Field { - kind: FieldErrorKind::InvalidValue { - field: "vault-id".to_string(), - reason: e.to_string(), - }, - location: location.clone(), + let vault_id = match optional_string(output, "vault-id") { + Some(id) => { + Some(OrderCfg::validate_vault_id(&id).map_err(|e| { + YamlError::Field { + kind: FieldErrorKind::InvalidValue { + field: "vault-id".to_string(), + reason: e.to_string(), + }, + location: location.clone(), + } + })?) } - })?), - None => None, - }; + None => None, + }; - Ok(OrderIOCfg { - token: order_token.map(Arc::new), - vault_id, + Ok(OrderIOCfg { + token: order_token.map(Arc::new), + vault_id, + }) }) - }) - .collect::, YamlError>>()?; + .collect::, YamlError>>()?; let order = OrderCfg { document: document.clone(), key: order_key.clone(), inputs, outputs, - network: network.ok_or( - ParseOrderConfigSourceError::NetworkNotFoundError(String::new()), - )?, + network: network + .ok_or(ParseOrderCfgError::NetworkNotFoundError(String::new()))?, deployer, orderbook, }; @@ -799,13 +784,13 @@ impl PartialEq for OrderCfg { } #[derive(Error, Debug, PartialEq)] -pub enum ParseOrderConfigSourceError { +pub enum ParseOrderCfgError { #[error("Failed to parse deployer")] - DeployerParseError(ParseDeployerConfigSourceError), + DeployerParseError(ParseDeployerCfgError), #[error("Failed to parse orderbook")] - OrderbookParseError(ParseOrderbookConfigSourceError), + OrderbookParseError(ParseOrderbookCfgError), #[error("Failed to parse token")] - TokenParseError(ParseTokenConfigSourceError), + TokenParseError(ParseTokenCfgError), #[error("Network not found for Order: {0}")] NetworkNotFoundError(String), #[error("Network does not match")] @@ -834,305 +819,36 @@ pub enum ParseOrderConfigSourceError { VaultParseError(#[from] alloy::primitives::ruint::ParseError), } -impl ParseOrderConfigSourceError { +impl ParseOrderCfgError { pub fn to_readable_msg(&self) -> String { match self { - ParseOrderConfigSourceError::DeployerParseError(err) => + ParseOrderCfgError::DeployerParseError(err) => err.to_readable_msg(), - ParseOrderConfigSourceError::OrderbookParseError(err) => + ParseOrderCfgError::OrderbookParseError(err) => err.to_readable_msg(), - ParseOrderConfigSourceError::TokenParseError(err) => + ParseOrderCfgError::TokenParseError(err) => err.to_readable_msg(), - ParseOrderConfigSourceError::NetworkNotFoundError(_) => + ParseOrderCfgError::NetworkNotFoundError(_) => "No network could be determined for this order. Please specify a network or ensure that tokens, deployers, or orderbooks have valid networks.".to_string(), - ParseOrderConfigSourceError::NetworkNotMatch => + ParseOrderCfgError::NetworkNotMatch => "The networks specified in your order configuration do not match. All components (tokens, deployers, orderbooks) must use the same network.".to_string(), - ParseOrderConfigSourceError::DeployerNetworkDoesNotMatch { expected, found } => + ParseOrderCfgError::DeployerNetworkDoesNotMatch { expected, found } => format!("Network mismatch in your YAML configuration: The deployer is using network '{}' but the order is using network '{}'. Please ensure all components use the same network.", found, expected), - ParseOrderConfigSourceError::OrderbookNetworkDoesNotMatch { expected, found } => + ParseOrderCfgError::OrderbookNetworkDoesNotMatch { expected, found } => format!("Network mismatch in your YAML configuration: The orderbook is using network '{}' but the order is using network '{}'. Please ensure all components use the same network.", found, expected), - ParseOrderConfigSourceError::InputTokenNetworkDoesNotMatch { key, expected, found } => + ParseOrderCfgError::InputTokenNetworkDoesNotMatch { key, expected, found } => format!("Network mismatch in your YAML configuration: The input token '{}' is using network '{}' but the order is using network '{}'. Please ensure all components use the same network.", key, found, expected), - ParseOrderConfigSourceError::OutputTokenNetworkDoesNotMatch { key, expected, found } => + ParseOrderCfgError::OutputTokenNetworkDoesNotMatch { key, expected, found } => format!("Network mismatch in your YAML configuration: The output token '{}' is using network '{}' but the order is using network '{}'. Please ensure all components use the same network.", key, found, expected), - ParseOrderConfigSourceError::VaultParseError(err) => + ParseOrderCfgError::VaultParseError(err) => format!("The vault ID in your YAML configuration is invalid. Please provide a valid number: {}", err), } } } - -impl OrderConfigSource { - pub fn try_into_order( - self, - deployers: &HashMap>, - orderbooks: &HashMap>, - tokens: &HashMap>, - ) -> Result { - let mut network = None; - - let deployer = self - .deployer - .map(|deployer_name| { - deployers - .get(&deployer_name) - .ok_or(ParseOrderConfigSourceError::DeployerParseError( - ParseDeployerConfigSourceError::NetworkNotFoundError(deployer_name.clone()), - )) - .map(|v| { - if let Some(n) = &network { - if v.network == *n { - Ok(v.clone()) - } else { - Err(ParseOrderConfigSourceError::NetworkNotMatch) - } - } else { - network = Some(v.network.clone()); - Ok(v.clone()) - } - })? - }) - .transpose()?; - - let orderbook = self - .orderbook - .map(|orderbook_name| { - orderbooks - .get(&orderbook_name) - .ok_or(ParseOrderConfigSourceError::OrderbookParseError( - ParseOrderbookConfigSourceError::NetworkNotFoundError( - orderbook_name.clone(), - ), - )) - .map(|v| { - if let Some(n) = &network { - if v.network == *n { - Ok(v.clone()) - } else { - Err(ParseOrderConfigSourceError::NetworkNotMatch) - } - } else { - network = Some(v.network.clone()); - Ok(v.clone()) - } - })? - }) - .transpose()?; - - let inputs = self - .inputs - .into_iter() - .map(|input| { - tokens - .get(&input.token) - .ok_or(ParseOrderConfigSourceError::TokenParseError( - ParseTokenConfigSourceError::NetworkNotFoundError(input.token.clone()), - )) - .map(|v| { - if let Some(n) = &network { - if v.network == *n { - Ok(OrderIOCfg { - token: Some(v.clone()), - vault_id: input.vault_id, - }) - } else { - Err(ParseOrderConfigSourceError::NetworkNotMatch) - } - } else { - network = Some(v.network.clone()); - Ok(OrderIOCfg { - token: Some(v.clone()), - vault_id: input.vault_id, - }) - } - })? - }) - .collect::, _>>()?; - - let outputs = self - .outputs - .into_iter() - .map(|output| { - tokens - .get(&output.token) - .ok_or(ParseOrderConfigSourceError::TokenParseError( - ParseTokenConfigSourceError::NetworkNotFoundError(output.token.clone()), - )) - .map(|v| { - if let Some(n) = &network { - if v.network == *n { - Ok(OrderIOCfg { - token: Some(v.clone()), - vault_id: output.vault_id, - }) - } else { - Err(ParseOrderConfigSourceError::NetworkNotMatch) - } - } else { - network = Some(v.network.clone()); - Ok(OrderIOCfg { - token: Some(v.clone()), - vault_id: output.vault_id, - }) - } - })? - }) - .collect::, _>>()?; - - Ok(OrderCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: String::new(), - inputs, - outputs, - network: network.ok_or(ParseOrderConfigSourceError::NetworkNotFoundError( - String::new(), - ))?, - deployer, - orderbook, - }) - } -} - #[cfg(test)] mod tests { - use yaml::tests::get_document; - use super::*; - use crate::test::*; - - #[test] - fn test_try_into_order_success() { - let mut networks = HashMap::new(); - let network = mock_network(); - networks.insert("Local Testnet".to_string(), network); - - let mut deployers = HashMap::new(); - let deployer = mock_deployer(); - deployers.insert("Deployer1".to_string(), deployer); - - let mut orderbooks = HashMap::new(); - let orderbook = mock_orderbook(); - orderbooks.insert("Orderbook1".to_string(), orderbook); - - let mut tokens = HashMap::new(); - let token_input = mock_token("Token1"); - let token_output = mock_token("Token2"); - tokens.insert("Token1".to_string(), token_input.clone()); - tokens.insert("Token2".to_string(), token_output.clone()); - - let order_string = OrderConfigSource { - deployer: Some("Deployer1".to_string()), - orderbook: Some("Orderbook1".to_string()), - inputs: vec![IOStringConfigSource { - token: "Token1".to_string(), - vault_id: Some(U256::from(1)), - }], - outputs: vec![IOStringConfigSource { - token: "Token2".to_string(), - vault_id: Some(U256::from(2)), - }], - }; - - let result = order_string.try_into_order(&deployers, &orderbooks, &tokens); - assert!(result.is_ok()); - let order = result.unwrap(); - - assert_eq!(order.network, networks["Local Testnet"]); - assert_eq!(order.deployer, Some(deployers["Deployer1"].clone())); - assert_eq!(order.orderbook, Some(orderbooks["Orderbook1"].clone())); - assert_eq!( - order - .inputs - .iter() - .map(|v| v.token.clone().unwrap()) - .collect::>(), - vec![token_input] - ); - assert_eq!( - order - .outputs - .iter() - .map(|v| v.token.clone().unwrap()) - .collect::>(), - vec![token_output] - ); - } - - #[test] - fn test_try_into_order_network_not_found_error() { - let order_string = OrderConfigSource { - deployer: None, - orderbook: None, - inputs: vec![], - outputs: vec![], - }; - - let result = order_string.try_into_order(&HashMap::new(), &HashMap::new(), &HashMap::new()); - assert!(matches!( - result, - Err(ParseOrderConfigSourceError::NetworkNotFoundError(_)) - )); - let error = result.unwrap_err(); - assert!(error - .to_readable_msg() - .contains("No network could be determined for this order")); - } - - #[test] - fn test_try_into_order_deployer_not_found_error() { - let deployers = HashMap::new(); // Empty deployer map - - let order_string = OrderConfigSource { - deployer: Some("Nonexistent Deployer".to_string()), - orderbook: None, - inputs: vec![], - outputs: vec![], - }; - - let result = order_string.try_into_order(&deployers, &HashMap::new(), &HashMap::new()); - assert!(matches!( - result, - Err(ParseOrderConfigSourceError::DeployerParseError(_)) - )); - } - - #[test] - fn test_try_into_order_orderbook_not_found_error() { - let orderbooks = HashMap::new(); // Empty orderbook map - - let order_string = OrderConfigSource { - deployer: None, - orderbook: Some("Nonexistent Orderbook".to_string()), - inputs: vec![], - outputs: vec![], - }; - - let result = order_string.try_into_order(&HashMap::new(), &orderbooks, &HashMap::new()); - assert!(matches!( - result, - Err(ParseOrderConfigSourceError::OrderbookParseError(_)) - )); - } - - #[test] - fn test_try_into_order_token_not_found_error() { - let tokens = HashMap::new(); // Empty token map - - let order_string = OrderConfigSource { - deployer: None, - orderbook: None, - inputs: vec![IOStringConfigSource { - token: "Nonexistent Token".to_string(), - vault_id: Some(U256::from(1)), - }], - outputs: vec![], - }; - - let result = order_string.try_into_order(&HashMap::new(), &HashMap::new(), &tokens); - assert!(matches!( - result, - Err(ParseOrderConfigSourceError::TokenParseError(_)) - )); - } + use yaml::tests::get_document; #[test] fn test_parse_orders_from_yaml() { diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index 091365d40..375c4c930 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -5,7 +5,7 @@ pub mod orderbook; use crate::{ NetworkCfg, ParseDeployerCfgError, ParseDeploymentCfgError, ParseNetworkCfgError, - ParseOrderConfigSourceError, ParseOrderbookCfgError, ParseScenarioCfgError, ParseTokenCfgError, + ParseOrderCfgError, ParseOrderbookCfgError, ParseScenarioCfgError, ParseTokenCfgError, TokenCfg, }; use alloy::primitives::ruint::ParseError as RuintParseError; @@ -182,7 +182,7 @@ pub enum YamlError { #[error(transparent)] ParseDeployerCfgError(#[from] ParseDeployerCfgError), #[error(transparent)] - ParseOrderConfigSourceError(#[from] ParseOrderConfigSourceError), + ParseOrderCfgError(#[from] ParseOrderCfgError), #[error(transparent)] ParseScenarioCfgError(#[from] ParseScenarioCfgError), #[error(transparent)] @@ -223,9 +223,7 @@ impl PartialEq for YamlError { (Self::ParseTokenCfgError(e1), Self::ParseTokenCfgError(e2)) => e1 == e2, (Self::ParseOrderbookCfgError(e1), Self::ParseOrderbookCfgError(e2)) => e1 == e2, (Self::ParseDeployerCfgError(e1), Self::ParseDeployerCfgError(e2)) => e1 == e2, - (Self::ParseOrderConfigSourceError(e1), Self::ParseOrderConfigSourceError(e2)) => { - e1 == e2 - } + (Self::ParseOrderCfgError(e1), Self::ParseOrderCfgError(e2)) => e1 == e2, (Self::ParseScenarioCfgError(e1), Self::ParseScenarioCfgError(e2)) => e1 == e2, (Self::ParseDeploymentCfgError(e1), Self::ParseDeploymentCfgError(e2)) => e1 == e2, (Self::ContextError(e1), Self::ContextError(e2)) => e1.to_string() == e2.to_string(), @@ -298,7 +296,7 @@ impl YamlError { "Deployer configuration error in your YAML: {}", err.to_readable_msg() ), - YamlError::ParseOrderConfigSourceError(err) => format!( + YamlError::ParseOrderCfgError(err) => format!( "Order configuration error in your YAML: {}", err.to_readable_msg() ), From 923bad0eb9328c782ad686bfa5e782680ab4219d Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:45:57 +0300 Subject: [PATCH 11/51] remove merge file --- crates/settings/src/chart.rs | 1 - crates/settings/src/lib.rs | 1 - crates/settings/src/merge.rs | 447 ----------------------------------- 3 files changed, 449 deletions(-) delete mode 100644 crates/settings/src/merge.rs diff --git a/crates/settings/src/chart.rs b/crates/settings/src/chart.rs index f4dfe0f5e..671ebd820 100644 --- a/crates/settings/src/chart.rs +++ b/crates/settings/src/chart.rs @@ -12,7 +12,6 @@ use std::{ sync::{Arc, RwLock}, }; use strict_yaml_rust::StrictYaml; -use thiserror::Error; #[cfg(target_family = "wasm")] use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 4d7093e45..a1d3f4cde 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -4,7 +4,6 @@ pub mod config; pub mod deployer; pub mod deployment; pub mod gui; -pub mod merge; pub mod metaboard; pub mod network; pub mod order; diff --git a/crates/settings/src/merge.rs b/crates/settings/src/merge.rs deleted file mode 100644 index e2d8ae123..000000000 --- a/crates/settings/src/merge.rs +++ /dev/null @@ -1,447 +0,0 @@ -#![allow(clippy::map_entry)] -use crate::*; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum MergeError { - #[error("There is already a network called {0}")] - NetworkCollision(String), - - #[error("There is already a subgraph called {0}")] - SubgraphCollision(String), - - #[error("There is already a metaboard called {0}")] - MetaboardCollision(String), - - #[error("There is already an orderbook called {0}")] - OrderbookCollision(String), - - #[error("There is already a token called {0}")] - TokenCollision(String), - - #[error("There is already a deployer called {0}")] - DeployerCollision(String), - - #[error("There is already an order called {0}")] - OrderCollision(String), - - #[error("There is already a scenario called {0}")] - ScenarioCollision(String), - - #[error("There is already a chart called {0}")] - ChartCollision(String), - - #[error("There is already a deployment called {0}")] - DeploymentCollision(String), - - #[error("There is already a remote networks definition called {0}")] - RemoteNetworksCollision(String), - - #[error("There is already a accounts called {0}")] - AccountsCollision(String), -} - -impl ConfigSource { - pub fn merge(&mut self, other: ConfigSource) -> Result<(), MergeError> { - // Networks - let networks = &mut self.networks; - for (key, value) in other.networks { - if networks.contains_key(&key) { - return Err(MergeError::NetworkCollision(key)); - } - networks.insert(key, value); - } - - // Remote Networks - let remote_networks = &mut self.using_networks_from; - for (key, value) in other.using_networks_from { - if remote_networks.contains_key(&key) { - return Err(MergeError::NetworkCollision(key)); - } - remote_networks.insert(key, value); - } - - // Subgraphs - let subgraphs = &mut self.subgraphs; - for (key, value) in other.subgraphs { - if subgraphs.contains_key(&key) { - return Err(MergeError::SubgraphCollision(key)); - } - subgraphs.insert(key, value); - } - - // Metaboards - let metaboards = &mut self.metaboards; - for (key, value) in other.metaboards { - if metaboards.contains_key(&key) { - return Err(MergeError::MetaboardCollision(key)); - } - metaboards.insert(key, value); - } - - // Orderbooks - let orderbooks = &mut self.orderbooks; - for (key, value) in other.orderbooks { - if orderbooks.contains_key(&key) { - return Err(MergeError::OrderbookCollision(key)); - } - orderbooks.insert(key, value); - } - - // Tokens - let tokens = &mut self.tokens; - for (key, value) in other.tokens { - if tokens.contains_key(&key) { - return Err(MergeError::TokenCollision(key)); - } - tokens.insert(key, value); - } - - // Deployers - let deployers = &mut self.deployers; - for (key, value) in other.deployers { - if deployers.contains_key(&key) { - return Err(MergeError::DeployerCollision(key)); - } - deployers.insert(key, value); - } - - // Orders - let orders = &mut self.orders; - for (key, value) in other.orders { - if orders.contains_key(&key) { - return Err(MergeError::OrderCollision(key)); - } - orders.insert(key, value); - } - - // Scenarios - let scenarios = &mut self.scenarios; - for (key, value) in other.scenarios { - if scenarios.contains_key(&key) { - return Err(MergeError::ScenarioCollision(key)); - } - scenarios.insert(key, value); - } - - // Charts - let charts = &mut self.charts; - for (key, value) in other.charts { - if charts.contains_key(&key) { - return Err(MergeError::ChartCollision(key)); - } - charts.insert(key, value); - } - - // Deployments - let deployments = &mut self.deployments; - for (key, value) in other.deployments { - if deployments.contains_key(&key) { - return Err(MergeError::DeploymentCollision(key)); - } - deployments.insert(key, value); - } - - // Sentry - self.sentry = match (self.sentry, other.sentry) { - (Some(a), None) => Ok(Some(a)), - (None, Some(b)) => Ok(Some(b)), - (None, None) => Ok(None), - (Some(_), Some(_)) => Err(MergeError::DeploymentCollision("sentry".into())), - }?; - - // Accounts - match (&mut self.accounts, other.accounts) { - (Some(accounts), Some(other_accounts)) => { - for (key, value) in other_accounts { - if accounts.contains_key(&key) { - return Err(MergeError::AccountsCollision(key)); - } - accounts.insert(key, value); - } - } - (None, Some(other_accounts)) => { - self.accounts = Some(other_accounts); - } - _ => {} - } - - Ok(()) - } -} - -impl Config { - pub fn merge(&mut self, other: Config) -> Result<(), MergeError> { - // Networks - let networks = &mut self.networks; - for (key, value) in other.networks { - if networks.contains_key(&key) { - return Err(MergeError::NetworkCollision(key)); - } - networks.insert(key, value.clone()); - } - - // Subgraphs - let subgraphs = &mut self.subgraphs; - for (key, value) in other.subgraphs { - if subgraphs.contains_key(&key) { - return Err(MergeError::SubgraphCollision(key)); - } - subgraphs.insert(key, value.clone()); - } - - // Metaboards - let metaboards = &mut self.metaboards; - for (key, value) in other.metaboards { - if metaboards.contains_key(&key) { - return Err(MergeError::MetaboardCollision(key)); - } - metaboards.insert(key, value.clone()); - } - - // Orderbooks - let orderbooks = &mut self.orderbooks; - for (key, value) in other.orderbooks { - if orderbooks.contains_key(&key) { - return Err(MergeError::OrderbookCollision(key)); - } - orderbooks.insert(key, value.clone()); - } - - // Tokens - let tokens = &mut self.tokens; - for (key, value) in other.tokens { - if tokens.contains_key(&key) { - return Err(MergeError::TokenCollision(key)); - } - tokens.insert(key, value.clone()); - } - - // Deployers - let deployers = &mut self.deployers; - for (key, value) in other.deployers { - if deployers.contains_key(&key) { - return Err(MergeError::DeployerCollision(key)); - } - deployers.insert(key, value.clone()); - } - - // Orders - let orders = &mut self.orders; - for (key, value) in other.orders { - if orders.contains_key(&key) { - return Err(MergeError::OrderCollision(key)); - } - orders.insert(key, value.clone()); - } - - // Scenarios - let scenarios = &mut self.scenarios; - for (key, value) in other.scenarios { - if scenarios.contains_key(&key) { - return Err(MergeError::ScenarioCollision(key)); - } - scenarios.insert(key, value.clone()); - } - - // Charts - let charts = &mut self.charts; - for (key, value) in other.charts { - if charts.contains_key(&key) { - return Err(MergeError::ChartCollision(key)); - } - charts.insert(key, value.clone()); - } - - // Deployments - let deployments = &mut self.deployments; - for (key, value) in other.deployments { - if deployments.contains_key(&key) { - return Err(MergeError::DeploymentCollision(key)); - } - deployments.insert(key, value); - } - - // Sentry - self.sentry = match (self.sentry, other.sentry) { - (Some(a), None) => Ok(Some(a)), - (None, Some(b)) => Ok(Some(b)), - (None, None) => Ok(None), - (Some(_), Some(_)) => Err(MergeError::DeploymentCollision("sentry".into())), - }?; - - // Accounts - match (&mut self.accounts, other.accounts) { - (Some(accounts), Some(other_accounts)) => { - for (key, value) in other_accounts { - if accounts.contains_key(&key) { - return Err(MergeError::AccountsCollision(key)); - } - accounts.insert(key, value); - } - } - (None, Some(other_accounts)) => { - self.accounts = Some(other_accounts); - } - _ => {} - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use url::Url; - - use super::*; - use std::collections::HashMap; - #[test] - fn test_successful_merge() { - let mut config = ConfigSource { - raindex_version: None, - using_networks_from: HashMap::new(), - subgraphs: HashMap::new(), - metaboards: HashMap::new(), - orderbooks: HashMap::new(), - tokens: HashMap::new(), - deployers: HashMap::new(), - orders: HashMap::new(), - scenarios: HashMap::new(), - charts: HashMap::new(), - networks: HashMap::new(), - deployments: HashMap::new(), - sentry: None, - accounts: None, - gui: None, - }; - - let other = ConfigSource { - raindex_version: None, - using_networks_from: HashMap::new(), - subgraphs: HashMap::new(), - metaboards: HashMap::new(), - orderbooks: HashMap::new(), - tokens: HashMap::new(), - deployers: HashMap::new(), - orders: HashMap::new(), - scenarios: HashMap::new(), - charts: HashMap::new(), - networks: HashMap::new(), - deployments: HashMap::new(), - sentry: None, - accounts: None, - gui: None, - }; - - assert_eq!(config.merge(other), Ok(())); - } - - #[test] - fn test_unsuccessful_merge() { - let mut config = ConfigSource { - raindex_version: None, - using_networks_from: HashMap::new(), - subgraphs: HashMap::new(), - metaboards: HashMap::new(), - orderbooks: HashMap::new(), - tokens: HashMap::new(), - deployers: HashMap::new(), - orders: HashMap::new(), - scenarios: HashMap::new(), - charts: HashMap::new(), - networks: HashMap::new(), - deployments: HashMap::new(), - sentry: None, - accounts: None, - gui: None, - }; - - let mut other = ConfigSource { - raindex_version: None, - using_networks_from: HashMap::new(), - subgraphs: HashMap::new(), - metaboards: HashMap::new(), - orderbooks: HashMap::new(), - tokens: HashMap::new(), - deployers: HashMap::new(), - orders: HashMap::new(), - scenarios: HashMap::new(), - charts: HashMap::new(), - networks: HashMap::new(), - deployments: HashMap::new(), - sentry: None, - accounts: None, - gui: None, - }; - - // Add a collision to cause an unsuccessful merge - config.subgraphs.insert( - "subgraph1".to_string(), - Url::parse("https://myurl").unwrap(), - ); - - other.subgraphs.insert( - "subgraph1".to_string(), - Url::parse("https://myurl").unwrap(), - ); - - assert_eq!( - config.merge(other), - Err(MergeError::SubgraphCollision("subgraph1".to_string())) - ); - } - - #[test] - fn test_successful_merge_metaboard() { - let mut config = ConfigSource { - raindex_version: None, - using_networks_from: HashMap::new(), - subgraphs: HashMap::new(), - metaboards: HashMap::new(), - orderbooks: HashMap::new(), - tokens: HashMap::new(), - deployers: HashMap::new(), - orders: HashMap::new(), - scenarios: HashMap::new(), - charts: HashMap::new(), - networks: HashMap::new(), - deployments: HashMap::new(), - sentry: None, - accounts: None, - gui: None, - }; - - let mut other = ConfigSource { - raindex_version: None, - using_networks_from: HashMap::new(), - subgraphs: HashMap::new(), - metaboards: HashMap::new(), - orderbooks: HashMap::new(), - tokens: HashMap::new(), - deployers: HashMap::new(), - orders: HashMap::new(), - scenarios: HashMap::new(), - charts: HashMap::new(), - networks: HashMap::new(), - deployments: HashMap::new(), - sentry: None, - accounts: None, - gui: None, - }; - - other.metaboards.insert( - "metaboard1".to_string(), - Url::parse("https://myurl").unwrap(), - ); - - assert!(config.metaboards.is_empty()); - - assert_eq!(config.merge(other), Ok(())); - - assert_eq!( - config.metaboards.get("metaboard1"), - Some(&Url::parse("https://myurl").unwrap()) - ); - } -} From ad23e1e000089aa7cfcb7a52e51d7cfe1f85af8d Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 13:49:15 +0300 Subject: [PATCH 12/51] update gui config --- crates/settings/src/gui.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/settings/src/gui.rs b/crates/settings/src/gui.rs index 32d40d16d..6a7ce8660 100644 --- a/crates/settings/src/gui.rs +++ b/crates/settings/src/gui.rs @@ -5,7 +5,7 @@ use crate::{ optional_vec, require_string, require_vec, FieldErrorKind, YamlError, YamlParsableHash, YamlParseableValue, }, - DeploymentCfg, TokenCfg, TokenCfgRef, + DeploymentCfg, TokenCfg, }; use alloy::primitives::{ruint::ParseError, utils::UnitsError}; use serde::{Deserialize, Serialize}; @@ -34,7 +34,7 @@ impl_wasm_traits!(GuiPresetSourceCfg); #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "kebab-case")] pub struct GuiDepositSourceCfg { - pub token: TokenCfgRef, + pub token: String, #[cfg_attr(target_family = "wasm", tsify(optional))] pub presets: Option>, } @@ -85,14 +85,14 @@ impl GuiConfigSourceCfg { self, deployments: &HashMap>, tokens: &HashMap>, - ) -> Result { + ) -> Result { let gui_deployments = self .deployments .iter() .map(|(deployment_name, deployment_source)| { let deployment = deployments .get(deployment_name) - .ok_or(ParseGuiConfigSourceError::DeploymentNotFoundError( + .ok_or(ParseGuiCfgError::DeploymentNotFoundError( deployment_name.clone(), )) .map(Arc::clone)?; @@ -103,7 +103,7 @@ impl GuiConfigSourceCfg { .map(|deposit_source| { let token = tokens .get(&deposit_source.token) - .ok_or(ParseGuiConfigSourceError::TokenNotFoundError( + .ok_or(ParseGuiCfgError::TokenNotFoundError( deposit_source.token.clone(), )) .map(Arc::clone)?; @@ -113,7 +113,7 @@ impl GuiConfigSourceCfg { presets: deposit_source.presets.clone(), }) }) - .collect::, ParseGuiConfigSourceError>>()?; + .collect::, ParseGuiCfgError>>()?; let fields = deployment_source .fields @@ -137,14 +137,14 @@ impl GuiConfigSourceCfg { value: preset.value.clone(), }) }) - .collect::, ParseGuiConfigSourceError>>() + .collect::, ParseGuiCfgError>>() }) .transpose()?, default: field_source.default.clone(), show_custom_field: field_source.show_custom_field, }) }) - .collect::, ParseGuiConfigSourceError>>()?; + .collect::, ParseGuiCfgError>>()?; Ok(( deployment_name.clone(), @@ -160,7 +160,7 @@ impl GuiConfigSourceCfg { }, )) }) - .collect::, ParseGuiConfigSourceError>>()?; + .collect::, ParseGuiCfgError>>()?; Ok(GuiCfg { name: self.name, @@ -171,7 +171,7 @@ impl GuiConfigSourceCfg { } #[derive(Error, Debug)] -pub enum ParseGuiConfigSourceError { +pub enum ParseGuiCfgError { #[error("Deployment not found: {0}")] DeploymentNotFoundError(String), #[error("Token not found: {0}")] @@ -208,7 +208,7 @@ impl_wasm_traits!(GuiDepositCfg); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] pub struct GuiSelectTokensCfg { - pub key: TokenCfgRef, + pub key: String, #[cfg_attr(target_family = "wasm", tsify(optional))] pub name: Option, #[cfg_attr(target_family = "wasm", tsify(optional))] From 79aee3d7ccc32c20e2c5b2a97cd3d8b11d45a098 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 16:00:40 +0300 Subject: [PATCH 13/51] add functions that return hashmaps for yaml structs --- crates/settings/src/yaml/dotrain.rs | 17 ++++++++++++++++- crates/settings/src/yaml/orderbook.rs | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/settings/src/yaml/dotrain.rs b/crates/settings/src/yaml/dotrain.rs index 67c025828..dc99e18b5 100644 --- a/crates/settings/src/yaml/dotrain.rs +++ b/crates/settings/src/yaml/dotrain.rs @@ -86,6 +86,10 @@ impl DotrainYaml { let orders = OrderCfg::parse_all_from_yaml(self.documents.clone(), None)?; Ok(orders.keys().cloned().collect()) } + pub fn get_orders(&self) -> Result, YamlError> { + let orders = OrderCfg::parse_all_from_yaml(self.documents.clone(), None)?; + Ok(orders) + } pub fn get_order(&self, key: &str) -> Result { let mut context = Context::new(); self.expand_context_with_current_order(&mut context, Some(key.to_string())); @@ -99,6 +103,10 @@ impl DotrainYaml { let scenarios = ScenarioCfg::parse_all_from_yaml(self.documents.clone(), None)?; Ok(scenarios.keys().cloned().collect()) } + pub fn get_scenarios(&self) -> Result, YamlError> { + let scenarios = ScenarioCfg::parse_all_from_yaml(self.documents.clone(), None)?; + Ok(scenarios) + } pub fn get_scenario(&self, key: &str) -> Result { ScenarioCfg::parse_from_yaml(self.documents.clone(), key, None) } @@ -107,6 +115,10 @@ impl DotrainYaml { let deployments = DeploymentCfg::parse_all_from_yaml(self.documents.clone(), None)?; Ok(deployments.keys().cloned().collect()) } + pub fn get_deployments(&self) -> Result, YamlError> { + let deployments = DeploymentCfg::parse_all_from_yaml(self.documents.clone(), None)?; + Ok(deployments) + } pub fn get_deployment(&self, key: &str) -> Result { let mut context = Context::new(); self.expand_context_with_current_deployment(&mut context, Some(key.to_string())); @@ -129,7 +141,10 @@ impl DotrainYaml { let charts = ChartCfg::parse_all_from_yaml(self.documents.clone(), None)?; Ok(charts.keys().cloned().collect()) } - + pub fn get_charts(&self) -> Result, YamlError> { + let charts = ChartCfg::parse_all_from_yaml(self.documents.clone(), None)?; + Ok(charts) + } pub fn get_chart(&self, key: &str) -> Result { ChartCfg::parse_from_yaml(self.documents.clone(), key, None) } diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index 9d084223b..fc0e795fa 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -122,6 +122,10 @@ impl OrderbookYaml { let tokens = TokenCfg::parse_all_from_yaml(self.documents.clone(), Some(&context))?; Ok(tokens.keys().cloned().collect()) } + pub fn get_tokens(&self) -> Result, YamlError> { + let context = self.initialize_context_and_expand_remote_data()?; + TokenCfg::parse_all_from_yaml(self.documents.clone(), Some(&context)) + } pub fn get_token(&self, key: &str) -> Result { let context = self.initialize_context_and_expand_remote_data()?; TokenCfg::parse_from_yaml(self.documents.clone(), key, Some(&context)) @@ -140,6 +144,10 @@ impl OrderbookYaml { let subgraphs = SubgraphCfg::parse_all_from_yaml(self.documents.clone(), None)?; Ok(subgraphs.keys().cloned().collect()) } + pub fn get_subgraphs(&self) -> Result, YamlError> { + let subgraphs = SubgraphCfg::parse_all_from_yaml(self.documents.clone(), None)?; + Ok(subgraphs) + } pub fn get_subgraph(&self, key: &str) -> Result { SubgraphCfg::parse_from_yaml(self.documents.clone(), key, None) } @@ -149,6 +157,10 @@ impl OrderbookYaml { let orderbooks = OrderbookCfg::parse_all_from_yaml(self.documents.clone(), Some(&context))?; Ok(orderbooks.keys().cloned().collect()) } + pub fn get_orderbooks(&self) -> Result, YamlError> { + let context = self.initialize_context_and_expand_remote_data()?; + OrderbookCfg::parse_all_from_yaml(self.documents.clone(), Some(&context)) + } pub fn get_orderbook(&self, key: &str) -> Result { let context = self.initialize_context_and_expand_remote_data()?; OrderbookCfg::parse_from_yaml(self.documents.clone(), key, Some(&context)) @@ -167,6 +179,10 @@ impl OrderbookYaml { let metaboards = MetaboardCfg::parse_all_from_yaml(self.documents.clone(), None)?; Ok(metaboards.keys().cloned().collect()) } + pub fn get_metaboards(&self) -> Result, YamlError> { + let metaboards = MetaboardCfg::parse_all_from_yaml(self.documents.clone(), None)?; + Ok(metaboards) + } pub fn get_metaboard(&self, key: &str) -> Result { MetaboardCfg::parse_from_yaml(self.documents.clone(), key, None) } @@ -179,6 +195,10 @@ impl OrderbookYaml { let deployers = DeployerCfg::parse_all_from_yaml(self.documents.clone(), Some(&context))?; Ok(deployers.keys().cloned().collect()) } + pub fn get_deployers(&self) -> Result, YamlError> { + let context = self.initialize_context_and_expand_remote_data()?; + DeployerCfg::parse_all_from_yaml(self.documents.clone(), Some(&context)) + } pub fn get_deployer(&self, key: &str) -> Result { let context = self.initialize_context_and_expand_remote_data()?; DeployerCfg::parse_from_yaml(self.documents.clone(), key, Some(&context)) From 5bac27d68b30baddad869887fdf8e1614d53082a Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 16 Apr 2025 16:06:59 +0300 Subject: [PATCH 14/51] update sentry return value --- crates/settings/src/yaml/orderbook.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index fc0e795fa..6a7de0f46 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -204,9 +204,9 @@ impl OrderbookYaml { DeployerCfg::parse_from_yaml(self.documents.clone(), key, Some(&context)) } - pub fn get_sentry(&self) -> Result { + pub fn get_sentry(&self) -> Result, YamlError> { let value = Sentry::parse_from_yaml_optional(self.documents[0].clone())?; - Ok(value.map_or(false, |v| v == "true")) + Ok(value.map(|v| v == "true")) } pub fn get_raindex_version(&self) -> Result, YamlError> { @@ -429,7 +429,7 @@ mod tests { "mainnet" ); - assert!(ob_yaml.get_sentry().unwrap()); + assert_eq!(ob_yaml.get_sentry().unwrap(), Some(true)); assert_eq!( ob_yaml.get_raindex_version().unwrap(), From bfb268f95859fb6c9b54eb3e7caec67af35a4604 Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 17 Apr 2025 08:32:40 +0300 Subject: [PATCH 15/51] update config --- crates/settings/src/config.rs | 661 +++++++++++++++++----------------- 1 file changed, 339 insertions(+), 322 deletions(-) diff --git a/crates/settings/src/config.rs b/crates/settings/src/config.rs index 7a1665067..ea5d9b72d 100644 --- a/crates/settings/src/config.rs +++ b/crates/settings/src/config.rs @@ -1,9 +1,10 @@ -use super::config_source::ConfigSourceError; +use crate::yaml::dotrain::DotrainYaml; +use crate::yaml::orderbook::OrderbookYaml; +use crate::yaml::{YamlError, YamlParsable}; use crate::*; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::sync::Arc; -use std::{collections::HashMap, sync::RwLock}; -use strict_yaml_rust::StrictYaml; use subgraph::SubgraphCfg; use thiserror::Error; use url::Url; @@ -21,235 +22,179 @@ pub struct Config { serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub networks: HashMap>, + networks: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub subgraphs: HashMap>, + subgraphs: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub metaboards: HashMap>, + metaboards: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub orderbooks: HashMap>, + orderbooks: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub tokens: HashMap>, + tokens: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub deployers: HashMap>, + deployers: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub orders: HashMap>, + orders: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub scenarios: HashMap>, + scenarios: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub charts: HashMap>, + charts: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), tsify(type = "Record") )] - pub deployments: HashMap>, + deployments: HashMap>, #[cfg_attr(target_family = "wasm", tsify(optional))] - pub sentry: Option, + sentry: Option, #[cfg_attr(target_family = "wasm", tsify(optional))] - pub raindex_version: Option, + raindex_version: Option, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_opt_hashmap_as_object"), tsify(type = "Record", optional) )] - pub accounts: Option>>, + accounts: Option>>, #[cfg_attr(target_family = "wasm", tsify(optional))] - pub gui: Option, + gui: Option, } #[cfg(target_family = "wasm")] impl_wasm_traits!(Config); #[derive(Error, Debug)] -pub enum ParseConfigSourceError { +pub enum ParseConfigError { #[error(transparent)] - ParseNetworkConfigSourceError(#[from] ParseNetworkConfigSourceError), + ParseNetworkCfgError(#[from] ParseNetworkCfgError), #[error(transparent)] - ParseOrderbookConfigSourceError(#[from] ParseOrderbookConfigSourceError), + ParseOrderbookCfgError(#[from] ParseOrderbookCfgError), #[error(transparent)] - ParseTokenConfigSourceError(#[from] ParseTokenConfigSourceError), + ParseTokenCfgError(#[from] ParseTokenCfgError), #[error(transparent)] - ParseOrderConfigSourceError(#[from] ParseOrderConfigSourceError), + ParseOrderCfgError(#[from] ParseOrderCfgError), #[error(transparent)] - ParseDeployerConfigSourceError(#[from] ParseDeployerConfigSourceError), + ParseDeployerCfgError(#[from] ParseDeployerCfgError), #[error(transparent)] - ParseScenarioConfigSourceError(#[from] ParseScenarioConfigSourceError), + ParseScenarioCfgError(#[from] ParseScenarioCfgError), #[error(transparent)] - ParseChartConfigSourceError(#[from] ParseChartConfigSourceError), + ParseDeploymentCfgError(#[from] ParseDeploymentCfgError), #[error(transparent)] - ParseDeploymentConfigSourceError(#[from] ParseDeploymentConfigSourceError), + ParseGuiCfgError(#[from] ParseGuiCfgError), #[error(transparent)] - ParseGuiConfigSourceError(#[from] ParseGuiConfigSourceError), - #[error("Failed to parse subgraph {}", 0)] - SubgraphParseError(url::ParseError), - #[error(transparent)] - YamlDeserializerError(#[from] serde_yaml::Error), - #[error(transparent)] - ConfigSourceError(#[from] ConfigSourceError), + YamlError(#[from] YamlError), + #[error("Network not found: {0}")] + NetworkNotFound(String), + #[error("Subgraph not found: {0}")] + SubgraphNotFound(String), + #[error("Metaboard not found: {0}")] + MetaboardNotFound(String), + #[error("Orderbook not found: {0}")] + OrderbookNotFound(String), + #[error("Token not found: {0}")] + OrderNotFound(String), + #[error("Deployer not found: {0}")] + TokenNotFound(String), + #[error("Order not found: {0}")] + DeployerNotFound(String), + #[error("Scenario not found: {0}")] + ScenarioNotFound(String), + #[error("Chart not found: {0}")] + ChartNotFound(String), + #[error("Deployment not found: {0}")] + DeploymentNotFound(String), } -impl TryFrom for Config { - type Error = ParseConfigSourceError; +impl Config { + pub fn try_from_settings(settings: Vec) -> Result { + let dotrain_yaml = DotrainYaml::new(settings.clone(), false)?; + let orderbook_yaml = OrderbookYaml::new(settings, false)?; - fn try_from(item: ConfigSource) -> Result { - let networks = item - .networks + let networks = orderbook_yaml + .get_networks()? .into_iter() - .map(|(name, network)| { - Ok(( - name.clone(), - Arc::new(network.try_into_network(name.clone())?), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - let subgraphs = item - .subgraphs + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let subgraphs = orderbook_yaml + .get_subgraphs()? .into_iter() - .map(|(name, subgraph)| { - Ok(( - name.clone(), - Arc::new(SubgraphCfg { - document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), - key: name.clone(), - url: subgraph.clone(), - }), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - let metaboards = item - .metaboards + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let metaboards = orderbook_yaml + .get_metaboards()? .into_iter() - .map(|(name, metaboard)| Ok((name, Arc::new(metaboard)))) - .collect::>, ParseConfigSourceError>>()?; - - let orderbooks = item - .orderbooks + .map(|(k, v)| (k, Arc::new(v.url))) + .collect::>(); + let orderbooks = orderbook_yaml + .get_orderbooks()? .into_iter() - .map(|(name, orderbook)| { - Ok(( - name.clone(), - Arc::new(orderbook.try_into_orderbook(name, &networks, &subgraphs)?), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - let tokens = item - .tokens + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let tokens = orderbook_yaml + .get_tokens()? .into_iter() - .map(|(name, token)| { - Ok(( - name.clone(), - Arc::new(token.try_into_token(&name, &networks)?), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - let deployers = item - .deployers + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let deployers = orderbook_yaml + .get_deployers()? .into_iter() - .map(|(name, deployer)| { - Ok(( - name.clone(), - Arc::new(deployer.try_into_deployer(name, &networks)?), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - let orders = item - .orders + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let orders = dotrain_yaml + .get_orders()? .into_iter() - .map(|(name, order)| { - Ok(( - name, - Arc::new(order.try_into_order(&deployers, &orderbooks, &tokens)?), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - // Initialize an empty HashMap for all scenarios - let mut scenarios = HashMap::new(); - - // Directly iterate over scenarios if it's a HashMap - for (name, scenario_string) in item.scenarios { - let scenario_map = scenario_string.try_into_scenarios( - name.clone(), - &ScenarioParent::default(), - &deployers, - )?; - - // Merge the scenarios - scenarios.extend(scenario_map); - } - - let deployments = item - .deployments + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let scenarios = dotrain_yaml + .get_scenarios()? .into_iter() - .map(|(name, deployment)| { - Ok(( - name, - Arc::new(deployment.try_into_deployment(&scenarios, &orders)?), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - let charts = item - .charts + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let deployments = dotrain_yaml + .get_deployments()? .into_iter() - .map(|(name, chart)| { - Ok(( - name.clone(), - Arc::new(chart.try_into_chart(name, &scenarios)?), - )) - }) - .collect::>, ParseConfigSourceError>>()?; - - let accounts = item.accounts.map(|wl| { - wl.into_iter() - .map(|(name, address)| (name, Arc::new(address))) - .collect::>>() - }); - - let gui = match item.gui { - Some(g) => Some(g.try_into_gui(&deployments, &tokens)?), - None => None, - }; + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let charts = dotrain_yaml + .get_charts()? + .into_iter() + .map(|(k, v)| (k, Arc::new(v))) + .collect::>(); + let sentry = orderbook_yaml.get_sentry()?; + let raindex_version = orderbook_yaml.get_raindex_version()?; + let gui = dotrain_yaml.get_gui(None)?; let config = Config { - raindex_version: item.raindex_version, networks, subgraphs, metaboards, @@ -260,197 +205,269 @@ impl TryFrom for Config { scenarios, charts, deployments, - sentry: item.sentry, - accounts, + sentry, + raindex_version, + // TODO: add accounts + accounts: None, gui, }; - Ok(config) } -} -impl Config { - pub async fn try_from_string(val: String) -> Result { - let config_source = ConfigSource::try_from_string(val, None).await?.0; - std::convert::TryInto::::try_into(config_source) + pub fn get_networks(&self) -> &HashMap> { + &self.networks + } + pub fn get_network(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.networks + .get(key) + .ok_or(ParseConfigError::NetworkNotFound(key.to_string())) } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy::primitives::Address; - use std::collections::HashMap; - use url::Url; - - #[test] - fn test_basic_conversion() { - let mut networks = HashMap::new(); - networks.insert( - "mainnet".to_string(), - NetworkConfigSource { - rpc: Url::parse("https://mainnet.node").unwrap(), - chain_id: 1, - label: Some("Ethereum Mainnet".to_string()), - network_id: Some(1), - currency: Some("ETH".to_string()), - }, - ); - - let mut subgraphs = HashMap::new(); - subgraphs.insert( - "mainnet".to_string(), - Url::parse("https://mainnet.subgraph").unwrap(), - ); - - let mut metaboards = HashMap::new(); - metaboards.insert( - "mainnet".to_string(), - Url::parse("https://mainnet.metaboard").unwrap(), - ); - - let mut orderbooks = HashMap::new(); - orderbooks.insert( - "mainnetOrderbook".to_string(), - OrderbookConfigSource { - address: "0x1234567890123456789012345678901234567890" - .parse::
() - .unwrap(), - network: Some("mainnet".to_string()), - subgraph: Some("mainnet".to_string()), - label: Some("Mainnet Orderbook".to_string()), - }, - ); - let mut tokens = HashMap::new(); - tokens.insert( - "ETH".to_string(), - TokenConfigSource { - network: "mainnet".to_string(), - address: "0x7890123456789012345678901234567890123456" - .parse::
() - .unwrap(), - decimals: Some(18), - label: Some("Ethereum".to_string()), - symbol: Some("ETH".to_string()), - }, - ); + pub fn get_subgraphs(&self) -> &HashMap> { + &self.subgraphs + } + pub fn get_subgraph(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.subgraphs + .get(key) + .ok_or(ParseConfigError::SubgraphNotFound(key.to_string())) + } - let mut deployers = HashMap::new(); - deployers.insert( - "mainDeployer".to_string(), - DeployerConfigSource { - address: "0xabcdef0123456789ABCDEF0123456789ABCDEF01" - .parse::
() - .unwrap(), - network: Some("mainnet".to_string()), - label: Some("Mainnet Deployer".to_string()), - }, - ); + pub fn get_metaboards(&self) -> &HashMap> { + &self.metaboards + } + pub fn get_metaboard(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.metaboards + .get(key) + .ok_or(ParseConfigError::MetaboardNotFound(key.to_string())) + } - let using_networks_from = HashMap::new(); - let orders = HashMap::new(); - let scenarios = HashMap::new(); - let charts = HashMap::new(); - let deployments = HashMap::new(); - let sentry = Some(true); - let accounts = Some(HashMap::from([( - "name-one".to_string(), - "address-one".to_string(), - )])); - let gui = Some(GuiConfigSourceCfg { - name: "Some name".to_string(), - description: "Some description".to_string(), - deployments: HashMap::new(), - }); + pub fn get_orderbooks(&self) -> &HashMap> { + &self.orderbooks + } + pub fn get_orderbook(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.orderbooks + .get(key) + .ok_or(ParseConfigError::OrderbookNotFound(key.to_string())) + } - let config_string = ConfigSource { - raindex_version: Some("0x123".to_string()), - using_networks_from, - networks, - subgraphs, - metaboards, - orderbooks, - tokens, - deployers, - orders, - scenarios, - charts, - deployments, - sentry, - accounts, - gui, - }; + pub fn get_tokens(&self) -> &HashMap> { + &self.tokens + } + pub fn get_token(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.tokens + .get(key) + .ok_or(ParseConfigError::TokenNotFound(key.to_string())) + } - let config_result = Config::try_from(config_string); - assert!(config_result.is_ok()); + pub fn get_deployers(&self) -> &HashMap> { + &self.deployers + } + pub fn get_deployer(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.deployers + .get(key) + .ok_or(ParseConfigError::DeployerNotFound(key.to_string())) + } - let config = config_result.unwrap(); + pub fn get_orders(&self) -> &HashMap> { + &self.orders + } + pub fn get_order(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.orders + .get(key) + .ok_or(ParseConfigError::OrderNotFound(key.to_string())) + } - // Verify networks - assert_eq!(config.networks.len(), 1); - let mainnet_network = config.networks.get("mainnet").unwrap(); - assert_eq!( - mainnet_network.rpc, - Url::parse("https://mainnet.node").unwrap() - ); - assert_eq!(mainnet_network.chain_id, 1); - assert_eq!(mainnet_network.key, "mainnet".to_string()); + pub fn get_scenarios(&self) -> &HashMap> { + &self.scenarios + } + pub fn get_scenario(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.scenarios + .get(key) + .ok_or(ParseConfigError::ScenarioNotFound(key.to_string())) + } - // Verify subgraphs - assert_eq!(config.subgraphs.len(), 1); - let mainnet_subgraph = config.subgraphs.get("mainnet").unwrap(); - assert_eq!(mainnet_subgraph.url.as_str(), "https://mainnet.subgraph/"); + pub fn get_deployments(&self) -> &HashMap> { + &self.deployments + } + pub fn get_deployment(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.deployments + .get(key) + .ok_or(ParseConfigError::DeploymentNotFound(key.to_string())) + } - // Verify orderbooks - assert_eq!(config.orderbooks.len(), 1); - let mainnet_orderbook = config.orderbooks.get("mainnetOrderbook").unwrap(); - assert_eq!( - mainnet_orderbook.address, - "0x1234567890123456789012345678901234567890" - .parse::
() - .unwrap() - ); + pub fn get_charts(&self) -> &HashMap> { + &self.charts + } + pub fn get_chart(&self, key: &str) -> Result<&Arc, ParseConfigError> { + self.charts + .get(key) + .ok_or(ParseConfigError::ChartNotFound(key.to_string())) + } - // Verify tokens - assert_eq!(config.tokens.len(), 1); - let eth_token = config.tokens.get("ETH").unwrap(); - assert_eq!( - eth_token.address, - "0x7890123456789012345678901234567890123456" - .parse::
() - .unwrap() - ); - assert_eq!(eth_token.decimals, Some(18)); + pub fn get_gui(&self) -> &Option { + &self.gui + } - // Verify deployers - assert_eq!(config.deployers.len(), 1); - let main_deployer = config.deployers.get("mainDeployer").unwrap(); - assert_eq!( - main_deployer.address, - "0xabcdef0123456789ABCDEF0123456789ABCDEF01" - .parse::
() - .unwrap() - ); + pub fn get_sentry(&self) -> &Option { + &self.sentry + } - // Verify sentry - assert!(config.sentry.unwrap()); + pub fn get_raindex_version(&self) -> &Option { + &self.raindex_version + } - // Verify raindex_version - assert_eq!(config.raindex_version, Some("0x123".to_string())); + pub fn get_accounts(&self) -> &Option>> { + &self.accounts + } +} - // Verify accounts - assert!(config.accounts.is_some()); - let accounts = config.accounts.as_ref().unwrap(); - assert_eq!(accounts.len(), 1); - let (name, address) = accounts.iter().next().unwrap(); - assert_eq!(name, "name-one"); - assert_eq!(address.as_str(), "address-one"); +#[cfg(test)] +mod tests { + use super::Config; + + const ORDERBOOK_YAML: &str = r#" + networks: + mainnet: + rpc: https://mainnet.infura.io + chain-id: 1 + testnet: + rpc: https://testnet.infura.io + chain-id: 1337 + tokens: + token1: + network: mainnet + address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + decimals: 18 + label: Wrapped Ether + symbol: WETH + token2: + network: mainnet + address: 0x0000000000000000000000000000000000000002 + decimals: 6 + label: USD Coin + symbol: USDC + deployers: + scenario1: + address: 0x0000000000000000000000000000000000000002 + network: mainnet + deployer2: + address: 0x0000000000000000000000000000000000000003 + network: testnet + "#; + const DOTRAIN_YAML: &str = r#" + orders: + order1: + inputs: + - token: token1 + vault-id: 1 + outputs: + - token: token2 + vault-id: 2 + scenarios: + scenario1: + bindings: + key1: value1 + scenarios: + scenario2: + bindings: + key2: value2 + scenarios: + runs: 10 + deployments: + deployment1: + order: order1 + scenario: scenario1.scenario2 + deployment2: + order: order1 + scenario: scenario1 + gui: + name: Test gui + description: Test description + short-description: Test short description + deployments: + deployment1: + name: Test deployment + description: Test description + deposits: + - token: token1 + presets: + - 100 + - 2000 + fields: + - binding: key1 + name: Binding test + presets: + - value: value2 + select-tokens: + - key: token2 + name: Test token + description: Test description + charts: + chart1: + scenario: scenario1.scenario2 + plots: + plot1: + title: Test title + subtitle: Test subtitle + marks: + - type: dot + options: + x: 1 + y: 2 + r: 3 + fill: red + stroke: blue + transform: + type: hexbin + content: + outputs: + x: 1 + y: 2 + r: 3 + z: 4 + stroke: green + fill: blue + options: + x: 1 + y: 2 + bin-width: 10 + - type: line + options: + transform: + type: binx + content: + outputs: + x: 1 + options: + thresholds: 10 + - type: recty + options: + x0: 1 + x1: 2 + y0: 3 + y1: 4 + x: + label: Test x label + anchor: start + label-anchor: start + label-arrow: none + y: + label: Test y label + anchor: start + label-anchor: start + label-arrow: none + margin: 10 + margin-left: 20 + margin-right: 30 + margin-top: 40 + margin-bottom: 50 + inset: 60 + "#; - // Verify gui - assert!(config.gui.is_some()); - let gui = config.gui.as_ref().unwrap(); - assert_eq!(gui.name, "Some name"); - assert_eq!(gui.description, "Some description"); + #[test] + fn test_try_from_settings() { + let config = + Config::try_from_settings(vec![ORDERBOOK_YAML.to_string(), DOTRAIN_YAML.to_string()]) + .unwrap(); } } From a9c7c35131103648764087a9c1d4814a3b96a9e8 Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 17 Apr 2025 08:35:55 +0300 Subject: [PATCH 16/51] fix function calls --- crates/common/src/dotrain_order/mod.rs | 4 +-- crates/common/src/fuzz/impls.rs | 41 +++++--------------------- crates/common/src/unit_tests/mod.rs | 18 +++-------- crates/settings/src/unit_test.rs | 34 +++++++++++++++++++-- tauri-app/src-tauri/src/error.rs | 12 ++------ 5 files changed, 47 insertions(+), 62 deletions(-) diff --git a/crates/common/src/dotrain_order/mod.rs b/crates/common/src/dotrain_order/mod.rs index 2a6c9681c..cd7fae21f 100644 --- a/crates/common/src/dotrain_order/mod.rs +++ b/crates/common/src/dotrain_order/mod.rs @@ -15,7 +15,7 @@ use rain_orderbook_app_settings::yaml::cache::Cache; use rain_orderbook_app_settings::yaml::{ default_document, dotrain::DotrainYaml, orderbook::OrderbookYaml, YamlError, YamlParsable, }; -use rain_orderbook_app_settings::ParseConfigSourceError; +use rain_orderbook_app_settings::ParseConfigError; use serde::{Deserialize, Serialize}; use thiserror::Error; #[cfg(target_family = "wasm")] @@ -37,7 +37,7 @@ impl PartialEq for DotrainOrder { #[derive(Error, Debug)] pub enum DotrainOrderError { #[error(transparent)] - ParseConfigSourceError(#[from] ParseConfigSourceError), + ParseConfigError(#[from] ParseConfigError), #[error("Scenario {0} not found")] ScenarioNotFound(String), diff --git a/crates/common/src/fuzz/impls.rs b/crates/common/src/fuzz/impls.rs index dfea8f824..4cd83369b 100644 --- a/crates/common/src/fuzz/impls.rs +++ b/crates/common/src/fuzz/impls.rs @@ -91,7 +91,7 @@ impl FuzzRunner { // find the scenario by name in the settings let scenario = self .settings - .scenarios + .get_scenarios() .get(name) .ok_or(FuzzRunnerError::ScenarioNotFound(name.into())) .cloned()?; @@ -229,7 +229,7 @@ impl FuzzRunner { } pub async fn make_chart_data(&self) -> Result { - let charts = self.settings.charts.clone(); + let charts = self.settings.get_charts(); let mut scenarios_data: HashMap = HashMap::new(); for (_, chart) in charts.clone() { @@ -262,7 +262,6 @@ mod tests { primitives::utils::parse_ether, providers::{ext::AnvilApi, Provider}, }; - use rain_orderbook_app_settings::config_source::ConfigSource; use rain_orderbook_test_fixtures::LocalEvm; #[tokio::test(flavor = "multi_thread", worker_threads = 10)] @@ -295,11 +294,7 @@ b: fuzzed; deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let settings = serde_yaml::from_str::(frontmatter).unwrap(); - let config = settings - .try_into() - .map_err(|e| println!("{:?}", e)) - .unwrap(); + let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -350,11 +345,7 @@ _: block-number(); end_block = last_block_number ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let settings = serde_yaml::from_str::(frontmatter).unwrap(); - let config = settings - .try_into() - .map_err(|e| println!("{:?}", e)) - .unwrap(); + let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -413,11 +404,7 @@ d: 4; deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let settings = serde_yaml::from_str::(frontmatter).unwrap(); - let config = settings - .try_into() - .map_err(|e| println!("{:?}", e)) - .unwrap(); + let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -472,11 +459,7 @@ _: context<4 4>(); deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let settings = serde_yaml::from_str::(frontmatter).unwrap(); - let config = settings - .try_into() - .map_err(|e| println!("{:?}", e)) - .unwrap(); + let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -521,11 +504,7 @@ _: context<50 50>(); deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let settings = serde_yaml::from_str::(frontmatter).unwrap(); - let config = settings - .try_into() - .map_err(|e| println!("{:?}", e)) - .unwrap(); + let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -564,11 +543,7 @@ _: context<1 0>(); deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let settings = serde_yaml::from_str::(frontmatter).unwrap(); - let config = settings - .try_into() - .map_err(|e| println!("{:?}", e)) - .unwrap(); + let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; diff --git a/crates/common/src/unit_tests/mod.rs b/crates/common/src/unit_tests/mod.rs index 77715f5bf..58320823d 100644 --- a/crates/common/src/unit_tests/mod.rs +++ b/crates/common/src/unit_tests/mod.rs @@ -322,7 +322,7 @@ impl TestRunner { self.test_setup.deployer = self .settings .main_config - .deployers + .get_deployers() .get(&self.settings.test_config.scenario_name) .ok_or(TestRunnerError::ScenarioNotFound( self.settings.test_config.scenario_name.clone(), @@ -369,28 +369,18 @@ impl TestRunner { #[cfg(test)] mod tests { use super::*; - use rain_orderbook_app_settings::{ - config_source::ConfigSource, unit_test::UnitTestConfigSource, - }; + use rain_orderbook_app_settings::unit_test::UnitTestConfigSource; use rain_orderbook_test_fixtures::LocalEvm; fn get_main_config(dotrain: &str) -> Config { let frontmatter = RainDocument::get_front_matter(dotrain).unwrap(); - let settings = serde_yaml::from_str::(frontmatter).unwrap(); - settings - .try_into() - .map_err(|e| println!("{:?}", e)) - .unwrap() + Config::try_from_settings(vec![frontmatter.to_string()]).unwrap() } fn get_test_config(test_dotrain: &str) -> TestConfig { let frontmatter = RainDocument::get_front_matter(test_dotrain).unwrap(); let source = serde_yaml::from_str::(frontmatter).unwrap(); - source - .test - .try_into_test_config() - .map_err(|e| println!("{:?}", e)) - .unwrap() + source.test.try_into_test_config() } #[tokio::test(flavor = "multi_thread", worker_threads = 10)] diff --git a/crates/settings/src/unit_test.rs b/crates/settings/src/unit_test.rs index 89c7f9482..1c03b9e9b 100644 --- a/crates/settings/src/unit_test.rs +++ b/crates/settings/src/unit_test.rs @@ -1,3 +1,4 @@ +use crate::blocks::BlocksCfg; use crate::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -6,6 +7,34 @@ use strict_yaml_rust::StrictYaml; #[cfg(target_family = "wasm")] use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "kebab-case")] +#[cfg_attr(target_family = "wasm", derive(Tsify))] +pub struct ScenarioConfigSource { + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + #[cfg_attr( + target_family = "wasm", + serde(serialize_with = "serialize_hashmap_as_object"), + tsify(optional, type = "Record") + )] + pub bindings: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub runs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub blocks: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub deployer: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[cfg_attr( + target_family = "wasm", + serde(serialize_with = "serialize_opt_hashmap_as_object"), + tsify(optional, type = "Record") + )] + pub scenarios: Option>, +} +#[cfg(target_family = "wasm")] +impl_wasm_traits!(ScenarioConfigSource); + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "kebab-case")] #[cfg_attr(target_family = "wasm", derive(Tsify))] @@ -44,7 +73,7 @@ pub struct TestConfig { impl_wasm_traits!(TestConfig); impl TestConfigSource { - pub fn try_into_test_config(self) -> Result { + pub fn try_into_test_config(self) -> TestConfig { let mut bindings = HashMap::new(); for (k, v) in &self.scenario.bindings { bindings.insert(k.to_string(), v.to_string()); @@ -65,7 +94,6 @@ impl TestConfigSource { scenario_name: self.scenario_name.clone(), scenario, }; - - Ok(config) + config } } diff --git a/tauri-app/src-tauri/src/error.rs b/tauri-app/src-tauri/src/error.rs index e4e58010a..7cec0d3b6 100644 --- a/tauri-app/src-tauri/src/error.rs +++ b/tauri-app/src-tauri/src/error.rs @@ -2,9 +2,7 @@ use alloy::hex::FromHexError; use alloy::primitives::ruint::{FromUintError, ParseError as FromUintParseError}; use alloy_ethers_typecast::{client::LedgerClientError, transaction::ReadableClientError}; use dotrain::error::ComposeError; -use rain_orderbook_app_settings::config::ParseConfigSourceError; -use rain_orderbook_app_settings::config_source::ConfigSourceError; -use rain_orderbook_app_settings::merge::MergeError; +use rain_orderbook_app_settings::ParseConfigError; use rain_orderbook_common::dotrain_order::DotrainOrderError; use rain_orderbook_common::fuzz::FuzzRunnerError; use rain_orderbook_common::remove_order::RemoveOrderArgsError; @@ -58,10 +56,7 @@ pub enum CommandError { FuzzRunnerError(#[from] FuzzRunnerError), #[error(transparent)] - MergeError(#[from] MergeError), - - #[error(transparent)] - ParseConfigSourceError(#[from] ParseConfigSourceError), + ParseConfigError(#[from] ParseConfigError), #[error(transparent)] ParseConfigYamlError(#[from] serde_yaml::Error), @@ -75,9 +70,6 @@ pub enum CommandError { #[error(transparent)] ComposeError(#[from] ComposeError), - #[error(transparent)] - ConfigSourceError(#[from] ConfigSourceError), - #[error(transparent)] DotrainOrderError(#[from] DotrainOrderError), From 71623ebb3fc27aeecc9e16c8cc7d9750bead9cfa Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 17 Apr 2025 12:00:58 +0300 Subject: [PATCH 17/51] update yaml methods --- crates/settings/src/yaml/dotrain.rs | 12 ++++-------- crates/settings/src/yaml/orderbook.rs | 22 ++++++---------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/crates/settings/src/yaml/dotrain.rs b/crates/settings/src/yaml/dotrain.rs index dc99e18b5..356e6f34f 100644 --- a/crates/settings/src/yaml/dotrain.rs +++ b/crates/settings/src/yaml/dotrain.rs @@ -83,8 +83,7 @@ impl ContextProvider for DotrainYaml { impl DotrainYaml { pub fn get_order_keys(&self) -> Result, YamlError> { - let orders = OrderCfg::parse_all_from_yaml(self.documents.clone(), None)?; - Ok(orders.keys().cloned().collect()) + Ok(self.get_orders()?.keys().cloned().collect()) } pub fn get_orders(&self) -> Result, YamlError> { let orders = OrderCfg::parse_all_from_yaml(self.documents.clone(), None)?; @@ -100,8 +99,7 @@ impl DotrainYaml { } pub fn get_scenario_keys(&self) -> Result, YamlError> { - let scenarios = ScenarioCfg::parse_all_from_yaml(self.documents.clone(), None)?; - Ok(scenarios.keys().cloned().collect()) + Ok(self.get_scenarios()?.keys().cloned().collect()) } pub fn get_scenarios(&self) -> Result, YamlError> { let scenarios = ScenarioCfg::parse_all_from_yaml(self.documents.clone(), None)?; @@ -112,8 +110,7 @@ impl DotrainYaml { } pub fn get_deployment_keys(&self) -> Result, YamlError> { - let deployments = DeploymentCfg::parse_all_from_yaml(self.documents.clone(), None)?; - Ok(deployments.keys().cloned().collect()) + Ok(self.get_deployments()?.keys().cloned().collect()) } pub fn get_deployments(&self) -> Result, YamlError> { let deployments = DeploymentCfg::parse_all_from_yaml(self.documents.clone(), None)?; @@ -138,8 +135,7 @@ impl DotrainYaml { } pub fn get_chart_keys(&self) -> Result, YamlError> { - let charts = ChartCfg::parse_all_from_yaml(self.documents.clone(), None)?; - Ok(charts.keys().cloned().collect()) + Ok(self.get_charts()?.keys().cloned().collect()) } pub fn get_charts(&self) -> Result, YamlError> { let charts = ChartCfg::parse_all_from_yaml(self.documents.clone(), None)?; diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index 6a7de0f46..ffc089d4d 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -99,9 +99,7 @@ impl OrderbookYaml { } pub fn get_network_keys(&self) -> Result, YamlError> { - let context = self.initialize_context_and_expand_remote_data()?; - let networks = NetworkCfg::parse_all_from_yaml(self.documents.clone(), Some(&context))?; - Ok(networks.keys().cloned().collect()) + Ok(self.get_networks()?.keys().cloned().collect()) } pub fn get_networks(&self) -> Result, YamlError> { let context = self.initialize_context_and_expand_remote_data()?; @@ -118,9 +116,7 @@ impl OrderbookYaml { } pub fn get_token_keys(&self) -> Result, YamlError> { - let context = self.initialize_context_and_expand_remote_data()?; - let tokens = TokenCfg::parse_all_from_yaml(self.documents.clone(), Some(&context))?; - Ok(tokens.keys().cloned().collect()) + Ok(self.get_tokens()?.keys().cloned().collect()) } pub fn get_tokens(&self) -> Result, YamlError> { let context = self.initialize_context_and_expand_remote_data()?; @@ -141,8 +137,7 @@ impl OrderbookYaml { } pub fn get_subgraph_keys(&self) -> Result, YamlError> { - let subgraphs = SubgraphCfg::parse_all_from_yaml(self.documents.clone(), None)?; - Ok(subgraphs.keys().cloned().collect()) + Ok(self.get_subgraphs()?.keys().cloned().collect()) } pub fn get_subgraphs(&self) -> Result, YamlError> { let subgraphs = SubgraphCfg::parse_all_from_yaml(self.documents.clone(), None)?; @@ -153,9 +148,7 @@ impl OrderbookYaml { } pub fn get_orderbook_keys(&self) -> Result, YamlError> { - let context = self.initialize_context_and_expand_remote_data()?; - let orderbooks = OrderbookCfg::parse_all_from_yaml(self.documents.clone(), Some(&context))?; - Ok(orderbooks.keys().cloned().collect()) + Ok(self.get_orderbooks()?.keys().cloned().collect()) } pub fn get_orderbooks(&self) -> Result, YamlError> { let context = self.initialize_context_and_expand_remote_data()?; @@ -176,8 +169,7 @@ impl OrderbookYaml { } pub fn get_metaboard_keys(&self) -> Result, YamlError> { - let metaboards = MetaboardCfg::parse_all_from_yaml(self.documents.clone(), None)?; - Ok(metaboards.keys().cloned().collect()) + Ok(self.get_metaboards()?.keys().cloned().collect()) } pub fn get_metaboards(&self) -> Result, YamlError> { let metaboards = MetaboardCfg::parse_all_from_yaml(self.documents.clone(), None)?; @@ -191,9 +183,7 @@ impl OrderbookYaml { } pub fn get_deployer_keys(&self) -> Result, YamlError> { - let context = self.initialize_context_and_expand_remote_data()?; - let deployers = DeployerCfg::parse_all_from_yaml(self.documents.clone(), Some(&context))?; - Ok(deployers.keys().cloned().collect()) + Ok(self.get_deployers()?.keys().cloned().collect()) } pub fn get_deployers(&self) -> Result, YamlError> { let context = self.initialize_context_and_expand_remote_data()?; From 0fd7b20c13f5403200c1c5d18991011ee560593b Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 17 Apr 2025 12:01:16 +0300 Subject: [PATCH 18/51] fix parse frontmatter logic --- crates/cli/src/commands/order/add.rs | 7 +++---- crates/common/src/frontmatter.rs | 8 +++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/cli/src/commands/order/add.rs b/crates/cli/src/commands/order/add.rs index bd219ca05..0bc51b501 100644 --- a/crates/cli/src/commands/order/add.rs +++ b/crates/cli/src/commands/order/add.rs @@ -3,7 +3,6 @@ use crate::{ }; use anyhow::{anyhow, Result}; use clap::{ArgAction, Args}; -use rain_orderbook_app_settings::Config; use rain_orderbook_common::add_order::AddOrderArgs; use rain_orderbook_common::frontmatter::parse_frontmatter; use rain_orderbook_common::transaction::TransactionArgs; @@ -40,10 +39,10 @@ pub struct CliOrderAddArgs { impl CliOrderAddArgs { async fn to_add_order_args(&self) -> Result { let text = read_to_string(&self.dotrain_file).map_err(|e| anyhow!(e))?; - let config: Config = parse_frontmatter(text.clone()).await?.try_into()?; + let config = parse_frontmatter(text.clone())?; if !self.skip_version_check { - if let Some(ver) = config.raindex_version { + if let Some(ver) = config.get_raindex_version() { if ver.to_ascii_lowercase() != GH_COMMIT_SHA.to_ascii_lowercase() { return Err(anyhow!(format!( "mismatch raindex version: expected: {}, got: {}", @@ -58,7 +57,7 @@ impl CliOrderAddArgs { } let config_deployment = config - .deployments + .get_deployments() .get(&self.deployment) .ok_or(anyhow!("specified deployment is undefined!"))?; diff --git a/crates/common/src/frontmatter.rs b/crates/common/src/frontmatter.rs index 1141d05ab..475202b97 100644 --- a/crates/common/src/frontmatter.rs +++ b/crates/common/src/frontmatter.rs @@ -1,11 +1,9 @@ use dotrain::RainDocument; pub use rain_metadata::types::authoring::v2::*; -use rain_orderbook_app_settings::{config::ParseConfigSourceError, config_source::ConfigSource}; +use rain_orderbook_app_settings::{Config, ParseConfigError}; /// Parse dotrain frontmatter and merges it with top Config if given -pub async fn parse_frontmatter(dotrain: String) -> Result { +pub fn parse_frontmatter(dotrain: String) -> Result { let frontmatter = RainDocument::get_front_matter(dotrain.as_str()).unwrap_or(""); - Ok(ConfigSource::try_from_string(frontmatter.to_string(), None) - .await? - .0) + Ok(Config::try_from_settings(vec![frontmatter.to_string()])?) } From d405697f5a90ca4ef8a7e22d3d98115ef0af4fc1 Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 17 Apr 2025 12:10:58 +0300 Subject: [PATCH 19/51] fix orderbook tests --- crates/cli/src/commands/chart/mod.rs | 7 ++-- crates/cli/src/commands/order/add.rs | 3 ++ crates/common/src/fuzz/impls.rs | 48 ++++++++++++++++++++++++---- crates/common/src/unit_tests/mod.rs | 32 ++++++++++++++++++- crates/settings/src/orderbook.rs | 17 ---------- crates/settings/src/token.rs | 8 ----- 6 files changed, 78 insertions(+), 37 deletions(-) diff --git a/crates/cli/src/commands/chart/mod.rs b/crates/cli/src/commands/chart/mod.rs index 7cacd690f..b83485987 100644 --- a/crates/cli/src/commands/chart/mod.rs +++ b/crates/cli/src/commands/chart/mod.rs @@ -1,7 +1,7 @@ use crate::execute::Execute; use anyhow::{anyhow, Result}; use clap::Args; -use rain_orderbook_app_settings::{config_source::ConfigSource, Config}; +use rain_orderbook_app_settings::Config; use rain_orderbook_common::dotrain::RainDocument; use rain_orderbook_common::fuzz::FuzzRunner; use std::fs::read_to_string; @@ -22,10 +22,7 @@ impl Execute for Chart { async fn execute(&self) -> Result<()> { let dotrain = read_to_string(self.dotrain_file.clone()).map_err(|e| anyhow!(e))?; let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config_string = ConfigSource::try_from_string(frontmatter.to_string(), None) - .await? - .0; - let config: Config = config_string.try_into()?; + let config = Config::try_from_settings(vec![frontmatter.to_string()])?; let fuzzer = FuzzRunner::new(&dotrain, config, None).await; let chart_data = fuzzer.make_chart_data().await?; diff --git a/crates/cli/src/commands/order/add.rs b/crates/cli/src/commands/order/add.rs index 0bc51b501..e927939bd 100644 --- a/crates/cli/src/commands/order/add.rs +++ b/crates/cli/src/commands/order/add.rs @@ -192,6 +192,9 @@ networks: subgraphs: some-sg: https://www.some-sg.com +metaboards: + some-metaboard: https://www.some-metaboard.com + deployers: some-deployer: network: some-network diff --git a/crates/common/src/fuzz/impls.rs b/crates/common/src/fuzz/impls.rs index 4cd83369b..0d0d815ba 100644 --- a/crates/common/src/fuzz/impls.rs +++ b/crates/common/src/fuzz/impls.rs @@ -264,6 +264,36 @@ mod tests { }; use rain_orderbook_test_fixtures::LocalEvm; + const SETTINGS: &str = r#" +subgraphs: + some-subgraph: https://www.some-subgraph.com +metaboards: + some-metaboard: https://www.some-metaboard.com +orderbooks: + some-orderbook: + address: 0x0000000000000000000000000000000000000000 + network: some-key + subgraph: some-subgraph +tokens: + token1: + network: some-key + address: 0x0000000000000000000000000000000000000001 + token2: + network: some-key + address: 0x0000000000000000000000000000000000000002 +orders: + some-order: + deployer: some-key + inputs: + - token: token1 + outputs: + - token: token2 +deployments: + some-deployment: + scenario: some-key + order: some-order +"#; + #[tokio::test(flavor = "multi_thread", worker_threads = 10)] async fn test_fuzz_runner() { let local_evm = LocalEvm::new().await; @@ -294,7 +324,8 @@ b: fuzzed; deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); + let config = + Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -345,7 +376,8 @@ _: block-number(); end_block = last_block_number ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); + let config = + Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -404,7 +436,8 @@ d: 4; deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); + let config = + Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -459,7 +492,8 @@ _: context<4 4>(); deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); + let config = + Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -504,7 +538,8 @@ _: context<50 50>(); deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); + let config = + Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -543,7 +578,8 @@ _: context<1 0>(); deployer = local_evm.deployer.address() ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config = Config::try_from_settings(vec![frontmatter.to_string()]).unwrap(); + let config = + Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()]).unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; diff --git a/crates/common/src/unit_tests/mod.rs b/crates/common/src/unit_tests/mod.rs index 58320823d..fd1b13133 100644 --- a/crates/common/src/unit_tests/mod.rs +++ b/crates/common/src/unit_tests/mod.rs @@ -372,9 +372,39 @@ mod tests { use rain_orderbook_app_settings::unit_test::UnitTestConfigSource; use rain_orderbook_test_fixtures::LocalEvm; + const SETTINGS: &str = r#" +subgraphs: + some-subgraph: https://www.some-subgraph.com +metaboards: + some-metaboard: https://www.some-metaboard.com +orderbooks: + some-orderbook: + address: 0x0000000000000000000000000000000000000000 + network: some-key + subgraph: some-subgraph +tokens: + token1: + network: some-key + address: 0x0000000000000000000000000000000000000001 + token2: + network: some-key + address: 0x0000000000000000000000000000000000000002 +orders: + some-order: + deployer: some-key + inputs: + - token: token1 + outputs: + - token: token2 +deployments: + some-deployment: + scenario: some-key + order: some-order +"#; + fn get_main_config(dotrain: &str) -> Config { let frontmatter = RainDocument::get_front_matter(dotrain).unwrap(); - Config::try_from_settings(vec![frontmatter.to_string()]).unwrap() + Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()]).unwrap() } fn get_test_config(test_dotrain: &str) -> TestConfig { diff --git a/crates/settings/src/orderbook.rs b/crates/settings/src/orderbook.rs index 131a04b94..5a915afd2 100644 --- a/crates/settings/src/orderbook.rs +++ b/crates/settings/src/orderbook.rs @@ -209,25 +209,8 @@ impl ParseOrderbookCfgError { #[cfg(test)] mod tests { use super::*; - use crate::test::*; use strict_yaml_rust::StrictYamlLoader; - fn setup() -> ( - HashMap>, - HashMap>, - ) { - let network = mock_network(); - let subgraph = mock_subgraph(); - - let mut networks = HashMap::new(); - networks.insert("TestNetwork".to_string(), network); - - let mut subgraphs = HashMap::new(); - subgraphs.insert("TestSubgraph".to_string(), subgraph); - - (networks, subgraphs) - } - fn get_document(yaml: &str) -> Arc> { let document = StrictYamlLoader::load_from_str(yaml).unwrap()[0].clone(); Arc::new(RwLock::new(document)) diff --git a/crates/settings/src/token.rs b/crates/settings/src/token.rs index 4b12269d5..8ca8c8d34 100644 --- a/crates/settings/src/token.rs +++ b/crates/settings/src/token.rs @@ -377,18 +377,10 @@ impl ParseTokenCfgError { #[cfg(test)] mod tests { - use self::test::*; use super::*; use alloy::primitives::Address; use yaml::tests::get_document; - fn setup_networks() -> HashMap> { - let network = mock_network(); - let mut networks = HashMap::new(); - networks.insert("TestNetwork".to_string(), network); - networks - } - #[test] fn test_parse_tokens_errors() { let error = TokenCfg::parse_all_from_yaml( From a16e3cabf400709d616f8a5036fad9fabc6206bd Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 17 Apr 2025 12:39:08 +0300 Subject: [PATCH 20/51] add tests to config --- crates/settings/src/config.rs | 327 +++++++++++++++++++++++++++++++++- 1 file changed, 325 insertions(+), 2 deletions(-) diff --git a/crates/settings/src/config.rs b/crates/settings/src/config.rs index ea5d9b72d..1fbe66157 100644 --- a/crates/settings/src/config.rs +++ b/crates/settings/src/config.rs @@ -323,9 +323,18 @@ impl Config { #[cfg(test)] mod tests { + use crate::{ + BinXTransformCfg, DotOptionsCfg, HexBinTransformCfg, LineOptionsCfg, MarkCfg, + RectYOptionsCfg, TransformCfg, + }; + use alloy::primitives::{Address, U256}; + use std::str::FromStr; + use url::Url; + use super::Config; const ORDERBOOK_YAML: &str = r#" + raindex-version: 0.1.0 networks: mainnet: rpc: https://mainnet.infura.io @@ -333,6 +342,21 @@ mod tests { testnet: rpc: https://testnet.infura.io chain-id: 1337 + subgraphs: + mainnet: https://mainnet-subgraph.com + testnet: https://testnet-subgraph.com + metaboards: + mainnet: https://mainnet-metaboard.com + testnet: https://testnet-metaboard.com + orderbooks: + mainnet: + address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + network: mainnet + subgraph: mainnet + testnet: + address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + network: testnet + subgraph: testnet tokens: token1: network: mainnet @@ -353,10 +377,13 @@ mod tests { deployer2: address: 0x0000000000000000000000000000000000000003 network: testnet + sentry: true "#; const DOTRAIN_YAML: &str = r#" orders: order1: + deployer: scenario1 + orderbook: mainnet inputs: - token: token1 vault-id: 1 @@ -371,8 +398,7 @@ mod tests { scenario2: bindings: key2: value2 - scenarios: - runs: 10 + runs: 10 deployments: deployment1: order: order1 @@ -469,5 +495,302 @@ mod tests { let config = Config::try_from_settings(vec![ORDERBOOK_YAML.to_string(), DOTRAIN_YAML.to_string()]) .unwrap(); + + assert_eq!(config.get_networks().len(), 2); + assert_eq!(config.get_subgraphs().len(), 2); + assert_eq!(config.get_metaboards().len(), 2); + assert_eq!(config.get_orderbooks().len(), 2); + assert_eq!(config.get_tokens().len(), 2); + assert_eq!(config.get_deployers().len(), 2); + assert_eq!(config.get_orders().len(), 1); + assert_eq!(config.get_scenarios().len(), 2); + assert_eq!(config.get_deployments().len(), 2); + assert_eq!(config.get_charts().len(), 1); + assert!(config.get_gui().is_some()); + assert!(config.get_sentry().is_some()); + assert!(config.get_raindex_version().is_some()); + assert!(config.get_accounts().is_none()); + + let mainnet_network = config.get_network("mainnet").unwrap(); + assert_eq!(mainnet_network.key, "mainnet"); + assert_eq!( + mainnet_network.rpc, + Url::parse("https://mainnet.infura.io").unwrap() + ); + assert_eq!(mainnet_network.chain_id, 1); + assert!(mainnet_network.label.is_none()); + assert!(mainnet_network.network_id.is_none()); + assert!(mainnet_network.currency.is_none()); + let testnet_network = config.get_network("testnet").unwrap(); + assert_eq!(testnet_network.key, "testnet"); + assert_eq!( + testnet_network.rpc, + Url::parse("https://testnet.infura.io").unwrap() + ); + assert_eq!(testnet_network.chain_id, 1337); + assert!(testnet_network.label.is_none()); + assert!(testnet_network.network_id.is_none()); + assert!(testnet_network.currency.is_none()); + + let mainnet_subgraph = config.get_subgraph("mainnet").unwrap(); + assert_eq!(mainnet_subgraph.key, "mainnet"); + assert_eq!( + mainnet_subgraph.url, + Url::parse("https://mainnet-subgraph.com").unwrap() + ); + let testnet_subgraph = config.get_subgraph("testnet").unwrap(); + assert_eq!(testnet_subgraph.key, "testnet"); + assert_eq!( + testnet_subgraph.url, + Url::parse("https://testnet-subgraph.com").unwrap() + ); + + let mainnet_metaboard = config.get_metaboard("mainnet").unwrap(); + assert_eq!( + **mainnet_metaboard, + Url::parse("https://mainnet-metaboard.com").unwrap() + ); + let testnet_metaboard = config.get_metaboard("testnet").unwrap(); + assert_eq!( + **testnet_metaboard, + Url::parse("https://testnet-metaboard.com").unwrap() + ); + + let mainnet_orderbook = config.get_orderbook("mainnet").unwrap(); + assert_eq!(mainnet_orderbook.key, "mainnet"); + assert_eq!( + mainnet_orderbook.address, + Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap() + ); + assert_eq!(mainnet_orderbook.network.key, "mainnet"); + assert_eq!(mainnet_orderbook.subgraph.key, "mainnet"); + assert!(mainnet_orderbook.label.is_none()); + let testnet_orderbook = config.get_orderbook("testnet").unwrap(); + assert_eq!(testnet_orderbook.key, "testnet"); + assert_eq!( + testnet_orderbook.address, + Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap() + ); + assert_eq!(testnet_orderbook.network.key, "testnet"); + assert_eq!(testnet_orderbook.subgraph.key, "testnet"); + assert!(testnet_orderbook.label.is_none()); + + let token1 = config.get_token("token1").unwrap(); + assert_eq!(token1.key, "token1"); + assert_eq!(token1.network.key, "mainnet"); + assert_eq!( + token1.address, + Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap() + ); + assert_eq!(token1.decimals, Some(18)); + assert_eq!(token1.label, Some("Wrapped Ether".to_string())); + assert_eq!(token1.symbol, Some("WETH".to_string())); + let token2 = config.get_token("token2").unwrap(); + assert_eq!(token2.key, "token2"); + assert_eq!(token2.network.key, "mainnet"); + assert_eq!( + token2.address, + Address::from_str("0x0000000000000000000000000000000000000002").unwrap() + ); + assert_eq!(token2.decimals, Some(6)); + assert_eq!(token2.label, Some("USD Coin".to_string())); + assert_eq!(token2.symbol, Some("USDC".to_string())); + + let deployer_scenario1 = config.get_deployer("scenario1").unwrap(); + assert_eq!(deployer_scenario1.key, "scenario1"); + assert_eq!( + deployer_scenario1.address, + Address::from_str("0x0000000000000000000000000000000000000002").unwrap() + ); + assert_eq!(deployer_scenario1.network.key, "mainnet"); + let deployer2 = config.get_deployer("deployer2").unwrap(); + assert_eq!(deployer2.key, "deployer2"); + assert_eq!( + deployer2.address, + Address::from_str("0x0000000000000000000000000000000000000003").unwrap() + ); + assert_eq!(deployer2.network.key, "testnet"); + + let order1 = config.get_order("order1").unwrap(); + assert_eq!(order1.key, "order1"); + assert_eq!(order1.network.key, "mainnet"); + assert_eq!(order1.deployer.clone().unwrap().key, "scenario1"); + assert_eq!(order1.orderbook.clone().unwrap().key, "mainnet"); + assert_eq!(order1.inputs.len(), 1); + let order1_input = &order1.inputs[0]; + assert_eq!(order1_input.token.as_ref().unwrap().key, "token1"); + assert_eq!(order1_input.vault_id, Some(U256::from(1))); + assert_eq!(order1.outputs.len(), 1); + let order1_output = &order1.outputs[0]; + assert_eq!(order1_output.token.as_ref().unwrap().key, "token2"); + assert_eq!(order1_output.vault_id, Some(U256::from(2))); + + let scenario1 = config.get_scenario("scenario1").unwrap(); + assert_eq!(scenario1.key, "scenario1"); + assert_eq!(scenario1.bindings.len(), 1); + assert_eq!(scenario1.bindings.get("key1").unwrap(), "value1"); + assert!(scenario1.runs.is_none()); + assert!(scenario1.blocks.is_none()); + assert_eq!(scenario1.deployer.key, "scenario1"); + let scenario1_scenario2 = config.get_scenario("scenario1.scenario2").unwrap(); + assert_eq!(scenario1_scenario2.key, "scenario1.scenario2"); + assert_eq!(scenario1_scenario2.bindings.len(), 2); + assert_eq!(scenario1_scenario2.bindings.get("key1").unwrap(), "value1"); + assert_eq!(scenario1_scenario2.bindings.get("key2").unwrap(), "value2"); + assert_eq!(scenario1_scenario2.runs.unwrap(), 10); + assert!(scenario1_scenario2.blocks.is_none()); + assert_eq!(scenario1_scenario2.deployer.key, "scenario1"); + + let deployment1 = config.get_deployment("deployment1").unwrap(); + assert_eq!(deployment1.key, "deployment1"); + assert_eq!(deployment1.order.key, "order1"); + assert_eq!(deployment1.scenario.key, "scenario1.scenario2"); + let deployment2 = config.get_deployment("deployment2").unwrap(); + assert_eq!(deployment2.key, "deployment2"); + assert_eq!(deployment2.order.key, "order1"); + assert_eq!(deployment2.scenario.key, "scenario1"); + + let chart1 = config.get_chart("chart1").unwrap(); + assert_eq!(chart1.key, "chart1"); + assert_eq!(chart1.scenario.key, "scenario1.scenario2"); + assert!(chart1.plots.is_some()); + assert!(chart1.metrics.is_none()); + let plots = chart1.plots.as_ref().unwrap(); + assert_eq!(plots.len(), 1); + let plot1 = &plots[0]; + assert_eq!(plot1.title, Some("Test title".to_string())); + assert_eq!(plot1.subtitle, Some("Test subtitle".to_string())); + assert_eq!(plot1.marks.len(), 3); + match &plot1.marks[0] { + MarkCfg::Dot(DotOptionsCfg { + x, + y, + r, + fill, + stroke, + transform, + }) => { + assert_eq!(x.as_deref(), Some("1")); + assert_eq!(y.as_deref(), Some("2")); + assert_eq!(r, &Some(3)); + assert_eq!(fill.as_deref(), Some("red")); + assert_eq!(stroke.as_deref(), Some("blue")); + assert!(transform.is_some()); + match transform.as_ref().unwrap() { + TransformCfg::HexBin(HexBinTransformCfg { outputs, options }) => { + assert_eq!(outputs.x.as_deref(), Some("1")); + assert_eq!(outputs.y.as_deref(), Some("2")); + assert_eq!(outputs.r, Some(3)); + assert_eq!(outputs.z.as_deref(), Some("4")); + assert_eq!(outputs.stroke.as_deref(), Some("green")); + assert_eq!(outputs.fill.as_deref(), Some("blue")); + assert_eq!(options.x.as_deref(), Some("1")); + assert_eq!(options.y.as_deref(), Some("2")); + assert_eq!(options.bin_width, Some(10)); + } + _ => panic!("Incorrect transform type for mark 0"), + } + } + _ => panic!("Incorrect mark type for mark 0"), + } + match &plot1.marks[1] { + MarkCfg::Line(LineOptionsCfg { transform, .. }) => { + assert!(transform.is_some()); + match transform.as_ref().unwrap() { + TransformCfg::BinX(BinXTransformCfg { outputs, options }) => { + assert_eq!(outputs.x.as_deref(), Some("1")); + // other outputs not specified, should be None + assert!(outputs.y.is_none()); + assert!(outputs.r.is_none()); + assert!(outputs.z.is_none()); + assert!(outputs.stroke.is_none()); + assert!(outputs.fill.is_none()); + // options x not specified, should be None + assert!(options.x.is_none()); + assert_eq!(options.thresholds, Some(10)); + } + _ => panic!("Incorrect transform type for mark 1"), + } + } + _ => panic!("Incorrect mark type for mark 1"), + } + match &plot1.marks[2] { + MarkCfg::RectY(RectYOptionsCfg { + x0, + x1, + y0, + y1, + transform, + }) => { + assert_eq!(x0.as_deref(), Some("1")); + assert_eq!(x1.as_deref(), Some("2")); + assert_eq!(y0.as_deref(), Some("3")); + assert_eq!(y1.as_deref(), Some("4")); + assert!(transform.is_none()); + } + _ => panic!("Incorrect mark type for mark 2"), + } + assert!(plot1.x.is_some()); + let axis_x = plot1.x.as_ref().unwrap(); + assert_eq!(axis_x.label, Some("Test x label".to_string())); + assert_eq!(axis_x.anchor, Some("start".to_string())); + assert_eq!(axis_x.label_anchor, Some("start".to_string())); + assert_eq!(axis_x.label_arrow, Some("none".to_string())); + assert!(plot1.y.is_some()); + let axis_y = plot1.y.as_ref().unwrap(); + assert_eq!(axis_y.label, Some("Test y label".to_string())); + assert_eq!(axis_y.anchor, Some("start".to_string())); + assert_eq!(axis_y.label_anchor, Some("start".to_string())); + assert_eq!(axis_y.label_arrow, Some("none".to_string())); + assert_eq!(plot1.margin, Some(10)); + assert_eq!(plot1.margin_left, Some(20)); + assert_eq!(plot1.margin_right, Some(30)); + assert_eq!(plot1.margin_top, Some(40)); + assert_eq!(plot1.margin_bottom, Some(50)); + assert_eq!(plot1.inset, Some(60)); + + let gui = config.get_gui().as_ref().unwrap(); + assert_eq!(gui.name, "Test gui"); + assert_eq!(gui.description, "Test description"); + assert_eq!(gui.deployments.len(), 1); + let gui_deployment1 = gui.deployments.get("deployment1").unwrap(); + assert_eq!(gui_deployment1.key, "deployment1"); + assert_eq!(gui_deployment1.deployment.key, "deployment1"); + assert_eq!(gui_deployment1.name, "Test deployment"); + assert_eq!(gui_deployment1.description, "Test description"); + assert_eq!(gui_deployment1.deposits.len(), 1); + let deposit1 = &gui_deployment1.deposits[0]; + assert_eq!(deposit1.token.as_ref().unwrap().key, "token1"); + assert_eq!( + deposit1.presets, + Some(vec!["100".to_string(), "2000".to_string()]) + ); + assert_eq!(gui_deployment1.fields.len(), 1); + let field1 = &gui_deployment1.fields[0]; + assert_eq!(field1.binding, "key1"); + assert_eq!(field1.name, "Binding test"); + assert!(field1.description.is_none()); + assert!(field1.presets.is_some()); + let field1_presets = field1.presets.as_ref().unwrap(); + assert_eq!(field1_presets.len(), 1); + assert!(field1_presets[0].name.is_none()); + assert_eq!(field1_presets[0].value, "value2"); + assert!(field1.default.is_none()); + assert!(field1.show_custom_field.is_none()); + assert!(gui_deployment1.select_tokens.is_some()); + let select_tokens = gui_deployment1.select_tokens.as_ref().unwrap(); + assert_eq!(select_tokens.len(), 1); + let select_token1 = &select_tokens[0]; + assert_eq!(select_token1.key, "token2"); + assert_eq!(select_token1.name, Some("Test token".to_string())); + assert_eq!( + select_token1.description, + Some("Test description".to_string()) + ); + + let sentry = config.get_sentry().unwrap(); + assert_eq!(sentry, true); + let raindex_version = config.get_raindex_version().as_ref().unwrap(); + assert_eq!(raindex_version, "0.1.0"); } } From 5efb7dbefc4e6c73b06c20bd630f99e0b4f30a3d Mon Sep 17 00:00:00 2001 From: findolor Date: Fri, 18 Apr 2025 09:47:22 +0300 Subject: [PATCH 21/51] refactor code --- .vscode/settings.json | 1 + Cargo.lock | 2 + Cargo.toml | 1 + crates/common/Cargo.toml | 1 + crates/common/src/js_api/mod.rs | 11 +- crates/js_api/src/gui/mod.rs | 6 +- crates/settings/Cargo.toml | 1 + crates/settings/src/config.rs | 30 ++-- crates/settings/src/metaboard.rs | 2 +- crates/settings/src/unit_test.rs | 4 +- packages/orderbook/test/common/test.test.ts | 42 +++++- .../src/lib/__mocks__/settings.ts | 40 ++++-- tauri-app/src-tauri/Cargo.lock | 2 + tauri-app/src-tauri/src/commands/charts.rs | 7 +- tauri-app/src-tauri/src/commands/config.rs | 21 +-- tauri-app/src-tauri/src/main.rs | 3 +- .../src/lib/components/ModalExecute.svelte | 2 +- .../src/lib/components/ModalExecute.test.ts | 8 +- tauri-app/src/lib/services/config.ts | 9 +- .../lib/services/configCodemirrorProblems.ts | 4 +- tauri-app/src/lib/services/pickConfig.ts | 12 +- tauri-app/src/lib/stores/settings.test.ts | 49 ++++--- tauri-app/src/lib/stores/settings.ts | 34 +++-- tauri-app/src/lib/stores/walletconnect.ts | 4 +- .../orders/[network]-[orderHash]/+page.svelte | 2 +- tauri-app/src/routes/orders/add/+page.svelte | 10 +- tauri-app/src/routes/settings/+page.svelte | 4 +- tauri-app/src/tests/pickConfig.test.ts | 132 +++++++----------- 28 files changed, 234 insertions(+), 210 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8bda8372f..3c282735c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "svelte.enable-ts-plugin": true, "rust-analyzer.linkedProjects": ["./Cargo.toml", "tauri-app/src-tauri/Cargo.toml"], + "rust-analyzer.cargo.target": "wasm32-unknown-unknown", "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", diff --git a/Cargo.lock b/Cargo.lock index 3738ca8b6..34b20cd4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6342,6 +6342,7 @@ dependencies = [ "tokio", "url", "wasm-bindgen-utils 0.0.7", + "web-sys", ] [[package]] @@ -6419,6 +6420,7 @@ dependencies = [ "tracing", "url", "wasm-bindgen-utils 0.0.7", + "web-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 84fbbb84a..3b3c74f81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ portpicker = "0.1.1" rain-erc = { git = "https://github.com/rainlanguage/rain.erc", rev = "0106e645ebd49334addc698c5aad9a85370eb54d" } rain-error-decoding = { git = "https://github.com/rainlanguage/rain.error", rev = "72d9577fdaf7135113847027ba951f9a43b41827" } wasm-bindgen-utils = "0.0.7" +web-sys = { version = "0.3.77", features = [ "console" ] } [workspace.dependencies.rain_orderbook_bindings] path = "crates/bindings" diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 2cb8a73d5..f4f6d8253 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -41,6 +41,7 @@ chrono = { workspace = true } futures = { workspace = true } rain-error-decoding = { workspace = true } rain-interpreter-eval = { workspace = true } +web-sys = { workspace = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/crates/common/src/js_api/mod.rs b/crates/common/src/js_api/mod.rs index 658c7d958..214123742 100644 --- a/crates/common/src/js_api/mod.rs +++ b/crates/common/src/js_api/mod.rs @@ -6,7 +6,7 @@ use crate::{ }; use alloy::primitives::Bytes; use js_sys::Uint8Array; -use rain_orderbook_app_settings::{Config, ParseConfigSourceError}; +use rain_orderbook_app_settings::ParseConfigError; use rain_orderbook_subgraph_client::types::common::SgOrder; use serde::{Deserialize, Serialize}; use std::ops::Deref; @@ -23,7 +23,7 @@ pub enum Error { #[error("undefined deployment")] UndefinedDeployment, #[error(transparent)] - ParseConfigSourceError(#[from] ParseConfigSourceError), + ParseConfigError(#[from] ParseConfigError), #[error(transparent)] AddOrderArgsError(#[from] AddOrderArgsError), #[error(transparent)] @@ -39,11 +39,8 @@ impl From for JsValue { /// Get addOrder() calldata from a given dotrain text and deployment key from its frontmatter #[wasm_bindgen(js_name = "getAddOrderCalldata")] pub async fn get_add_order_calldata(dotrain: &str, deployment: &str) -> Result { - let config: Config = parse_frontmatter(dotrain.to_string()).await?.try_into()?; - let deployment_ref = config - .deployments - .get(deployment) - .ok_or(Error::UndefinedDeployment)?; + let config = parse_frontmatter(dotrain.to_string())?; + let deployment_ref = config.get_deployment(deployment)?; let add_order_args = AddOrderArgs::new_from_deployment(dotrain.to_string(), deployment_ref.deref().clone()) .await?; diff --git a/crates/js_api/src/gui/mod.rs b/crates/js_api/src/gui/mod.rs index c87532f04..95fa8aac3 100644 --- a/crates/js_api/src/gui/mod.rs +++ b/crates/js_api/src/gui/mod.rs @@ -6,7 +6,7 @@ use rain_orderbook_app_settings::{ deployment::DeploymentCfg, gui::{ GuiCfg, GuiDeploymentCfg, GuiFieldDefinitionCfg, GuiPresetCfg, NameAndDescriptionCfg, - ParseGuiConfigSourceError, + ParseGuiCfgError, }, network::NetworkCfg, order::OrderCfg, @@ -317,7 +317,7 @@ pub enum GuiError { #[error(transparent)] DotrainOrderError(#[from] DotrainOrderError), #[error(transparent)] - ParseGuiConfigSourceError(#[from] ParseGuiConfigSourceError), + ParseGuiCfgError(#[from] ParseGuiCfgError), #[error(transparent)] IoError(#[from] std::io::Error), #[error(transparent)] @@ -399,7 +399,7 @@ impl GuiError { format!("A JavaScript error occurred: {}", msg), GuiError::DotrainOrderError(err) => format!("Order configuration error in YAML: {}", err), - GuiError::ParseGuiConfigSourceError(err) => + GuiError::ParseGuiCfgError(err) => format!("Failed to parse YAML GUI configuration: {}", err), GuiError::IoError(err) => format!("I/O error: {}", err), diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 48db05421..027e439bb 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -20,6 +20,7 @@ strict-yaml-rust = { workspace = true } alloy = { workspace = true, features = ["serde", "rand"] } reqwest = { workspace = true } rain_orderbook_bindings = { workspace = true } +web-sys = { workspace = true } [target.'cfg(target_family = "wasm")'.dependencies] wasm-bindgen-utils = { workspace = true } diff --git a/crates/settings/src/config.rs b/crates/settings/src/config.rs index 1fbe66157..f8ff33c14 100644 --- a/crates/settings/src/config.rs +++ b/crates/settings/src/config.rs @@ -141,52 +141,62 @@ impl Config { let orderbook_yaml = OrderbookYaml::new(settings, false)?; let networks = orderbook_yaml - .get_networks()? + .get_networks() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let subgraphs = orderbook_yaml - .get_subgraphs()? + .get_subgraphs() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let metaboards = orderbook_yaml - .get_metaboards()? + .get_metaboards() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v.url))) .collect::>(); let orderbooks = orderbook_yaml - .get_orderbooks()? + .get_orderbooks() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let tokens = orderbook_yaml - .get_tokens()? + .get_tokens() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let deployers = orderbook_yaml - .get_deployers()? + .get_deployers() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let orders = dotrain_yaml - .get_orders()? + .get_orders() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let scenarios = dotrain_yaml - .get_scenarios()? + .get_scenarios() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let deployments = dotrain_yaml - .get_deployments()? + .get_deployments() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); let charts = dotrain_yaml - .get_charts()? + .get_charts() + .unwrap_or_default() .into_iter() .map(|(k, v)| (k, Arc::new(v))) .collect::>(); diff --git a/crates/settings/src/metaboard.rs b/crates/settings/src/metaboard.rs index c5c09c1d6..38e889f41 100644 --- a/crates/settings/src/metaboard.rs +++ b/crates/settings/src/metaboard.rs @@ -94,7 +94,7 @@ impl YamlParsableHash for MetaboardCfg { if let Ok(metaboards_hash) = require_hash(&document_read, Some("metaboards"), None) { for (key_yaml, metaboard_yaml) in metaboards_hash { let metaboard_key = key_yaml.as_str().unwrap_or_default().to_string(); - let location = format!("metaboards[{}]", metaboard_key); + let location = format!("metaboards '{}'", metaboard_key); let url_str = require_string(metaboard_yaml, None, Some(location.clone()))?; let url = diff --git a/crates/settings/src/unit_test.rs b/crates/settings/src/unit_test.rs index 1c03b9e9b..ced51d0cd 100644 --- a/crates/settings/src/unit_test.rs +++ b/crates/settings/src/unit_test.rs @@ -5,7 +5,9 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock}; use strict_yaml_rust::StrictYaml; #[cfg(target_family = "wasm")] -use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; +use wasm_bindgen_utils::{ + impl_wasm_traits, prelude::*, serialize_hashmap_as_object, serialize_opt_hashmap_as_object, +}; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "kebab-case")] diff --git a/packages/orderbook/test/common/test.test.ts b/packages/orderbook/test/common/test.test.ts index 2ff919a8a..78af24793 100644 --- a/packages/orderbook/test/common/test.test.ts +++ b/packages/orderbook/test/common/test.test.ts @@ -19,6 +19,9 @@ networks: subgraphs: some-sg: https://www.some-sg.com +metaboards: + test: https://metaboard.com + deployers: some-deployer: network: some-network @@ -112,16 +115,46 @@ _ _: 0 0; assert.fail('expected to fail, but resolved'); } catch (error) { assert.ok(error instanceof Error); - assert.equal(error.message, 'undefined deployment'); + assert.equal(error.message, 'Deployment not found: some-other-deployment'); } }); it('should throw frontmatter missing field error', async () => { try { const dotrain = ` +networks: + some-network: + rpc: http://localhost:8080/rpc-url + chain-id: 123 + network-id: 123 + currency: ETH +subgraphs: + some-sg: https://www.some-sg.com +metaboards: + test: https://metaboard.com +orderbooks: + some-orderbook: + address: 0xc95A5f8eFe14d7a20BD2E5BAFEC4E71f8Ce0B9A6 + network: some-network + subgraph: some-sg +tokens: + token1: + network: some-network + address: 0xc2132d05d31c914a87c6611c10748aeb04b58e8f +orders: + some-order: + deployer: some-deployer + inputs: + - token: token1 + outputs: + - token: token1 +scenarios: + some-deployer: + bindings: + key: 10 deployers: some-deployer: - --- +--- #calculate-io _ _: 0 0; #handle-io @@ -133,10 +166,7 @@ _ _: 0 0; assert.fail('expected to fail, but resolved'); } catch (error) { assert.ok(error instanceof Error); - assert.equal( - error.message, - 'deployers.some-deployer: missing field `address` at line 3 column 19' - ); + assert.equal(error.message, "Missing required field 'address' in deployer 'some-deployer'"); } }); diff --git a/packages/ui-components/src/lib/__mocks__/settings.ts b/packages/ui-components/src/lib/__mocks__/settings.ts index 66399bcd4..cf6d88dc2 100644 --- a/packages/ui-components/src/lib/__mocks__/settings.ts +++ b/packages/ui-components/src/lib/__mocks__/settings.ts @@ -1,31 +1,51 @@ -import type { ConfigSource } from '@rainlanguage/orderbook'; +import type { Config } from '@rainlanguage/orderbook'; import { writable } from 'svelte/store'; -export const mockConfigSource: ConfigSource = { +export const mockConfigSource: Config = { networks: { mainnet: { + key: 'mainnet', rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', - 'chain-id': 1, + chainId: 1, label: 'Ethereum Mainnet', currency: 'ETH' } }, subgraphs: { - mainnet: 'https://api.thegraph.com/subgraphs/name/mainnet' + mainnet: { + key: 'mainnet', + url: 'https://api.thegraph.com/subgraphs/name/mainnet' + } }, orderbooks: { orderbook1: { + key: 'orderbook1', address: '0xOrderbookAddress1', - network: 'mainnet', - subgraph: 'uniswap', + network: { + key: 'mainnet', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH' + }, + subgraph: { + key: 'uniswap', + url: 'https://api.thegraph.com/subgraphs/name/uniswap' + }, label: 'Orderbook 1' } }, deployers: { deployer1: { + key: 'deployer1', address: '0xDeployerAddress1', - network: 'mainnet', - label: 'Deployer 1' + network: { + key: 'mainnet', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH' + } } }, metaboards: { @@ -35,6 +55,6 @@ export const mockConfigSource: ConfigSource = { name_one: 'address_one', name_two: 'address_two' } -}; +} as unknown as Config; -export const mockSettingsStore = writable(mockConfigSource); +export const mockSettingsStore = writable(mockConfigSource); diff --git a/tauri-app/src-tauri/Cargo.lock b/tauri-app/src-tauri/Cargo.lock index 897f41618..36fa2ca47 100644 --- a/tauri-app/src-tauri/Cargo.lock +++ b/tauri-app/src-tauri/Cargo.lock @@ -8278,6 +8278,7 @@ dependencies = [ "thiserror", "url", "wasm-bindgen-utils", + "web-sys", ] [[package]] @@ -8324,6 +8325,7 @@ dependencies = [ "tracing", "url", "wasm-bindgen-utils", + "web-sys", ] [[package]] diff --git a/tauri-app/src-tauri/src/commands/charts.rs b/tauri-app/src-tauri/src/commands/charts.rs index 4caf033bc..758c7d714 100644 --- a/tauri-app/src-tauri/src/commands/charts.rs +++ b/tauri-app/src-tauri/src/commands/charts.rs @@ -1,13 +1,10 @@ use crate::commands::config::merge_configstrings; use crate::error::CommandResult; -use rain_orderbook_app_settings::config::*; use rain_orderbook_common::fuzz::*; #[tauri::command] pub async fn make_charts(dotrain: String, settings: String) -> CommandResult { - let config = merge_configstrings(dotrain.clone(), settings).await?; - let final_config: Config = config.try_into()?; - let fuzzer = FuzzRunner::new(dotrain.as_str(), final_config.clone(), None).await; - + let config = merge_configstrings(dotrain.clone(), settings)?; + let fuzzer = FuzzRunner::new(dotrain.as_str(), config.clone(), None).await; Ok(fuzzer.make_chart_data().await?) } diff --git a/tauri-app/src-tauri/src/commands/config.rs b/tauri-app/src-tauri/src/commands/config.rs index 9b9a434a8..e8d6b5645 100644 --- a/tauri-app/src-tauri/src/commands/config.rs +++ b/tauri-app/src-tauri/src/commands/config.rs @@ -1,27 +1,16 @@ use crate::error::CommandResult; use dotrain::RainDocument; -use rain_orderbook_app_settings::{config::Config, config_source::ConfigSource}; +use rain_orderbook_app_settings::config::Config; #[tauri::command] -pub async fn parse_configstring(text: String) -> CommandResult { - Ok(ConfigSource::try_from_string(text, None).await?.0) +pub fn parse_configstring(text: String) -> CommandResult { + Ok(Config::try_from_settings(vec![text])?) } #[tauri::command] -pub async fn merge_configstrings( - dotrain: String, - config_text: String, -) -> CommandResult { +pub fn merge_configstrings(dotrain: String, config_text: String) -> CommandResult { let frontmatter = RainDocument::get_front_matter(dotrain.as_str()) .unwrap_or("") .to_string(); - let (mut dotrain_config, config) = - ConfigSource::try_from_string(frontmatter, Some(config_text)).await?; - dotrain_config.merge(config)?; - Ok(dotrain_config) -} - -#[tauri::command] -pub fn convert_configstring_to_config(config_string: ConfigSource) -> CommandResult { - Ok(config_string.try_into()?) + Ok(Config::try_from_settings(vec![frontmatter, config_text])?) } diff --git a/tauri-app/src-tauri/src/main.rs b/tauri-app/src-tauri/src/main.rs index 853505919..b7a92126a 100644 --- a/tauri-app/src-tauri/src/main.rs +++ b/tauri-app/src-tauri/src/main.rs @@ -11,7 +11,7 @@ use commands::app::get_app_commit_sha; use commands::authoring_meta::get_authoring_meta_v2_for_scenarios; use commands::chain::{get_block_number, get_chainid}; use commands::charts::make_charts; -use commands::config::{convert_configstring_to_config, merge_configstrings, parse_configstring}; +use commands::config::{merge_configstrings, parse_configstring}; use commands::dotrain::parse_dotrain; use commands::dotrain_add_order_lsp::{call_lsp_completion, call_lsp_hover, call_lsp_problems}; use commands::order::{ @@ -59,7 +59,6 @@ fn run_tauri_app() { call_lsp_problems, parse_configstring, merge_configstrings, - convert_configstring_to_config, make_charts, order_add_calldata, order_remove_calldata, diff --git a/tauri-app/src/lib/components/ModalExecute.svelte b/tauri-app/src/lib/components/ModalExecute.svelte index 844294b63..9a3403546 100644 --- a/tauri-app/src/lib/components/ModalExecute.svelte +++ b/tauri-app/src/lib/components/ModalExecute.svelte @@ -33,7 +33,7 @@ const getNetworkName = (chainId: number) => { const existingNetwork = Object.entries($settings?.networks || {}).find( - (entry) => entry[1]['chain-id'] === chainId, + (entry) => entry[1].chainId === chainId, ); if (existingNetwork) { diff --git a/tauri-app/src/lib/components/ModalExecute.test.ts b/tauri-app/src/lib/components/ModalExecute.test.ts index 8862a6970..42e1cd317 100644 --- a/tauri-app/src/lib/components/ModalExecute.test.ts +++ b/tauri-app/src/lib/components/ModalExecute.test.ts @@ -25,6 +25,7 @@ vi.mock('$lib/stores/settings', async (importOriginal) => ({ // Import components and stores after mocks import ModalExecute from './ModalExecute.svelte'; import { settings } from '$lib/stores/settings'; +import type { Config } from '@rainlanguage/orderbook'; describe('ModalExecute', () => { beforeEach(() => { @@ -32,7 +33,7 @@ describe('ModalExecute', () => { // Reset settings store before each test settings.set({ networks: {}, - }); + } as unknown as Config); }); describe('network connection error', () => { @@ -63,11 +64,12 @@ describe('ModalExecute', () => { settings.set({ networks: { mainnet: { - 'chain-id': 1, + key: 'mainnet', + chainId: 1, rpc: 'https://mainnet.com', }, }, - }); + } as unknown as Config); render(ModalExecute, { props: { diff --git a/tauri-app/src/lib/services/config.ts b/tauri-app/src/lib/services/config.ts index 782ad3070..ea17f602c 100644 --- a/tauri-app/src/lib/services/config.ts +++ b/tauri-app/src/lib/services/config.ts @@ -1,13 +1,10 @@ import { settingsText } from '$lib/stores/settings'; -import type { Config, ConfigSource } from '@rainlanguage/orderbook'; +import type { Config } from '@rainlanguage/orderbook'; import { invoke } from '@tauri-apps/api'; import { get } from 'svelte/store'; -export const parseConfigSource = async (text: string): Promise => +export const parseConfig = async (text: string): Promise => invoke('parse_configstring', { text }); -export const mergeDotrainConfigWithSettings = async (dotrain: string): Promise => +export const mergeDotrainConfigWithSettings = async (dotrain: string): Promise => invoke('merge_configstrings', { dotrain, configText: get(settingsText) }); - -export const convertConfigstringToConfig = async (configString: ConfigSource): Promise => - invoke('convert_configstring_to_config', { configString }); diff --git a/tauri-app/src/lib/services/configCodemirrorProblems.ts b/tauri-app/src/lib/services/configCodemirrorProblems.ts index 9e2b434ce..cf988c99d 100644 --- a/tauri-app/src/lib/services/configCodemirrorProblems.ts +++ b/tauri-app/src/lib/services/configCodemirrorProblems.ts @@ -1,12 +1,12 @@ import { ErrorCode, type Problem } from 'codemirror-rainlang'; import { reportErrorToSentry, SentrySeverityLevel } from '$lib/services/sentry'; -import { mergeDotrainConfigWithSettings, parseConfigSource } from './config'; +import { mergeDotrainConfigWithSettings, parseConfig } from './config'; export async function parseConfigSourceProblems(text: string) { const problems: Problem[] = []; try { - await parseConfigSource(text); + parseConfig(text); } catch (e) { reportErrorToSentry(e, SentrySeverityLevel.Info); problems.push(convertErrorToProblem(e)); diff --git a/tauri-app/src/lib/services/pickConfig.ts b/tauri-app/src/lib/services/pickConfig.ts index e649f0f4c..e4093df92 100644 --- a/tauri-app/src/lib/services/pickConfig.ts +++ b/tauri-app/src/lib/services/pickConfig.ts @@ -1,17 +1,15 @@ import { pickBy, isNil } from 'lodash'; -import type { Config, ConfigSource } from '@rainlanguage/orderbook'; +import type { Config } from '@rainlanguage/orderbook'; export function pickDeployments( - mergedConfigSource: ConfigSource | undefined, mergedConfig: Config | undefined, activeNetworkRef: string | undefined, ) { - return !isNil(mergedConfigSource) && - !isNil(mergedConfigSource?.deployments) && - !isNil(mergedConfigSource?.orders) + return !isNil(mergedConfig) && !isNil(mergedConfig?.deployments) && !isNil(mergedConfig?.orders) ? pickBy( - mergedConfigSource.deployments, - (d) => mergedConfig?.scenarios?.[d.scenario]?.deployer?.network?.key === activeNetworkRef, + mergedConfig.deployments, + (d) => + mergedConfig?.scenarios?.[d.scenario.key]?.deployer?.network?.key === activeNetworkRef, ) : {}; } diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts index 7181176c9..77639ac4f 100644 --- a/tauri-app/src/lib/stores/settings.test.ts +++ b/tauri-app/src/lib/stores/settings.test.ts @@ -3,34 +3,45 @@ import { settings, activeAccountsItems, activeSubgraphs } from './settings'; import { get } from 'svelte/store'; // Import the ConfigSource type -import type { ConfigSource } from '@rainlanguage/orderbook'; +import type { Config, NetworkCfg, SubgraphCfg } from '@rainlanguage/orderbook'; // Define the mock directly in the tests -const mockConfigSource: ConfigSource = { +const mockConfig: Config = { networks: { mainnet: { + key: 'mainnet', rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', - 'chain-id': 1, + chainId: 1, label: 'Ethereum Mainnet', currency: 'ETH', }, }, subgraphs: { - mainnet: 'https://api.thegraph.com/subgraphs/name/mainnet', + mainnet: { + key: 'mainnet', + url: 'https://api.thegraph.com/subgraphs/name/mainnet', + }, }, orderbooks: { orderbook1: { + key: 'orderbook1', address: '0xOrderbookAddress1', - network: 'mainnet', - subgraph: 'uniswap', + network: { + key: 'mainnet', + } as unknown as NetworkCfg, + subgraph: { + key: 'uniswap', + } as unknown as SubgraphCfg, label: 'Orderbook 1', }, }, deployers: { deployer1: { + key: 'deployer1', address: '0xDeployerAddress1', - network: 'mainnet', - label: 'Deployer 1', + network: { + key: 'mainnet', + } as unknown as NetworkCfg, }, }, metaboards: { @@ -40,7 +51,7 @@ const mockConfigSource: ConfigSource = { name_one: 'address_one', name_two: 'address_two', }, -}; +} as unknown as Config; describe('Settings active accounts items', () => { // Reset store values before each test to prevent state leakage @@ -51,7 +62,7 @@ describe('Settings active accounts items', () => { activeSubgraphs.set({}); // Then set our initial test values - settings.set(mockConfigSource); + settings.set(mockConfig); activeAccountsItems.set({ name_one: 'address_one', name_two: 'address_two', @@ -61,7 +72,7 @@ describe('Settings active accounts items', () => { }); // Verify initial state - expect(get(settings)).toEqual(mockConfigSource); + expect(get(settings)).toEqual(mockConfig); expect(get(activeAccountsItems)).toEqual({ name_one: 'address_one', name_two: 'address_two', @@ -74,7 +85,7 @@ describe('Settings active accounts items', () => { test('should remove account if that account is removed', () => { // Test removing an account const newSettings = { - ...mockConfigSource, + ...mockConfig, accounts: { name_one: 'address_one', }, @@ -91,7 +102,7 @@ describe('Settings active accounts items', () => { test('should remove account if the value is different', () => { const newSettings = { - ...mockConfigSource, + ...mockConfig, accounts: { name_one: 'address_one', name_two: 'new_value', @@ -107,37 +118,37 @@ describe('Settings active accounts items', () => { test('should update active subgraphs when subgraph value changes', () => { const newSettings = { - ...mockConfigSource, + ...mockConfig, subgraphs: { mainnet: 'new value', }, }; - settings.set(newSettings); + settings.set(newSettings as unknown as Config); expect(get(activeSubgraphs)).toEqual({}); }); test('should update active subgraphs when subgraph removed', () => { const newSettings = { - ...mockConfigSource, + ...mockConfig, subgraphs: { testnet: 'testnet', }, }; - settings.set(newSettings); + settings.set(newSettings as unknown as Config); expect(get(activeSubgraphs)).toEqual({}); }); test('should reset active subgraphs when subgraphs are undefined', () => { const newSettings = { - ...mockConfigSource, + ...mockConfig, subgraphs: undefined, }; - settings.set(newSettings); + settings.set(newSettings as unknown as Config); expect(get(activeSubgraphs)).toEqual({}); }); diff --git a/tauri-app/src/lib/stores/settings.ts b/tauri-app/src/lib/stores/settings.ts index 48e70d18d..9f2baf294 100644 --- a/tauri-app/src/lib/stores/settings.ts +++ b/tauri-app/src/lib/stores/settings.ts @@ -6,11 +6,7 @@ import { import find from 'lodash/find'; import * as chains from 'viem/chains'; import { textFileStore } from '$lib/storesGeneric/textFileStore'; -import { - type ConfigSource, - type OrderbookCfgRef, - type OrderbookConfigSource, -} from '@rainlanguage/orderbook'; +import { type Config, type OrderbookCfg } from '@rainlanguage/orderbook'; import { getBlockNumberFromRpc } from '$lib/services/chain'; import { pickBy } from 'lodash'; @@ -26,13 +22,13 @@ export const settingsFile = textFileStore( ['yml', 'yaml'], get(settingsText), ); -export const settings = cachedWritableStore( +export const settings = cachedWritableStore( 'settings', undefined, (value) => JSON.stringify(value), (str) => { try { - return JSON.parse(str) as ConfigSource; + return JSON.parse(str) as Config; } catch { return undefined; } @@ -53,7 +49,7 @@ export const activeNetwork = asyncDerived( }, ); export const rpcUrl = derived(activeNetwork, ($activeNetwork) => $activeNetwork?.rpc); -export const chainId = derived(activeNetwork, ($activeNetwork) => $activeNetwork?.['chain-id']); +export const chainId = derived(activeNetwork, ($activeNetwork) => $activeNetwork?.chainId); export const activeChain = derived(chainId, ($activeChainId) => find(Object.values(chains), (c) => c.id === $activeChainId), ); @@ -72,9 +68,9 @@ export const activeNetworkOrderbooks = derived( $settings?.orderbooks ? (pickBy( $settings.orderbooks, - (orderbook) => orderbook.network === $activeNetworkRef, - ) as Record) - : ({} as Record), + (orderbook) => orderbook.network.key === $activeNetworkRef, + ) as Record) + : ({} as Record), ); export const activeOrderbook = derived( [settings, activeOrderbookRef], @@ -85,7 +81,7 @@ export const activeOrderbook = derived( ); export const subgraphUrl = derived([settings, activeOrderbook], ([$settings, $activeOrderbook]) => $settings?.subgraphs !== undefined && $activeOrderbook?.subgraph !== undefined - ? $settings.subgraphs[$activeOrderbook.subgraph] + ? $settings.subgraphs[$activeOrderbook.subgraph.key] : undefined, ); export const orderbookAddress = derived( @@ -188,12 +184,14 @@ settings.subscribe(async () => { } else { const currentActiveSubgraphs = get(activeSubgraphs); const updatedActiveSubgraphs = Object.fromEntries( - Object.entries($settings.subgraphs).filter(([key, value]) => { - if (key in currentActiveSubgraphs) { - return currentActiveSubgraphs[key] === value; - } - return false; - }), + Object.entries($settings.subgraphs) + .filter(([key, value]) => { + if (key in currentActiveSubgraphs) { + return currentActiveSubgraphs[key] === value.key; + } + return false; + }) + .map(([key, value]) => [key, value.key]), ); activeSubgraphs.set(updatedActiveSubgraphs); } diff --git a/tauri-app/src/lib/stores/walletconnect.ts b/tauri-app/src/lib/stores/walletconnect.ts index dc365ef15..b79e634e3 100644 --- a/tauri-app/src/lib/stores/walletconnect.ts +++ b/tauri-app/src/lib/stores/walletconnect.ts @@ -73,8 +73,8 @@ export async function walletconnectConnect(priorityChainIds: number[]) { if ($settings?.networks) { for (const network of Object.values($settings.networks)) { - rpcMap[network['chain-id']] = network.rpc; - chains.push(network['chain-id']); + rpcMap[network.chainId] = network.rpc; + chains.push(network.chainId); } try { await walletconnectProvider?.connect({ diff --git a/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte b/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte index 956d5b533..585817647 100644 --- a/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte +++ b/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte @@ -19,7 +19,7 @@ const { orderHash, network } = $page.params; const orderbookAddress = $settings?.orderbooks?.[network]?.address as Hex; - const subgraphUrl = $settings?.subgraphs?.[network]; + const subgraphUrl = $settings?.subgraphs?.[network].url; const rpcUrl = $settings?.networks?.[network]?.rpc; function onRemove(order: SgOrder) { diff --git a/tauri-app/src/routes/orders/add/+page.svelte b/tauri-app/src/routes/orders/add/+page.svelte index c7a3b988a..2dce68f1d 100644 --- a/tauri-app/src/routes/orders/add/+page.svelte +++ b/tauri-app/src/routes/orders/add/+page.svelte @@ -13,7 +13,6 @@ import type { Config } from '@rainlanguage/orderbook'; import { DropdownRadio } from '@rainlanguage/ui-components'; import { toasts } from '$lib/stores/toasts'; - import type { ConfigSource } from '@rainlanguage/orderbook'; import ModalExecute from '$lib/components/ModalExecute.svelte'; import { orderAdd, @@ -26,10 +25,7 @@ import { promiseTimeout, CodeMirrorRainlang } from '@rainlanguage/ui-components'; import { SentrySeverityLevel, reportErrorToSentry } from '$lib/services/sentry'; import { pickScenarios } from '$lib/services/pickConfig'; - import { - convertConfigstringToConfig, - mergeDotrainConfigWithSettings, - } from '$lib/services/config'; + import { mergeDotrainConfigWithSettings } from '$lib/services/config'; import { mergeDotrainConfigWithSettingsProblems } from '$lib/services/configCodemirrorProblems'; import ScenarioDebugTable from '$lib/components/ScenarioDebugTable.svelte'; import { useDebouncedFn } from '$lib/utils/asyncDebounce'; @@ -44,7 +40,6 @@ let chartData: ChartData; let deploymentRef: string | undefined = undefined; let scenarioRef: string | undefined = undefined; - let mergedConfigSource: ConfigSource | undefined = undefined; let mergedConfig: Config | undefined = undefined; let openAddOrderModal = false; @@ -126,8 +121,7 @@ async function updateMergedConfig() { try { - mergedConfigSource = await mergeDotrainConfigWithSettings($globalDotrainFile.text); - mergedConfig = await convertConfigstringToConfig(mergedConfigSource); + mergedConfig = await mergeDotrainConfigWithSettings($globalDotrainFile.text); } catch (e) { reportErrorToSentry(e, SentrySeverityLevel.Info); } diff --git a/tauri-app/src/routes/settings/+page.svelte b/tauri-app/src/routes/settings/+page.svelte index 1c4fe1fc0..1c01bdf5c 100644 --- a/tauri-app/src/routes/settings/+page.svelte +++ b/tauri-app/src/routes/settings/+page.svelte @@ -5,7 +5,7 @@ import CodeMirrorConfigSource from '$lib/components/CodeMirrorConfigSource.svelte'; import FileTextarea from '$lib/components/FileTextarea.svelte'; import { useDebouncedFn } from '$lib/utils/asyncDebounce'; - import { parseConfigSource } from '$lib/services/config'; + import { parseConfig } from '$lib/services/config'; import { reportErrorToSentry, SentrySeverityLevel } from '$lib/services/sentry'; import { onMount } from 'svelte'; import { CheckOutline, CloseOutline } from 'flowbite-svelte-icons'; @@ -29,7 +29,7 @@ settingsStatus = 'checking'; try { settingsText.set(settingsContent); - settings.set(await parseConfigSource(settingsContent)); + settings.set(await parseConfig(settingsContent)); settingsStatus = 'success'; } catch (error) { errorMessage = error as string; diff --git a/tauri-app/src/tests/pickConfig.test.ts b/tauri-app/src/tests/pickConfig.test.ts index e17a17c3c..94bb63c4f 100644 --- a/tauri-app/src/tests/pickConfig.test.ts +++ b/tauri-app/src/tests/pickConfig.test.ts @@ -1,12 +1,7 @@ import { expect, test } from 'vitest'; import type { Dictionary } from 'lodash'; import { pickDeployments, pickScenarios } from '$lib/services/pickConfig'; -import type { - Config, - ConfigSource, - DeploymentConfigSource, - ScenarioCfg, -} from '@rainlanguage/orderbook'; +import type { Config, DeploymentCfg, ScenarioCfg } from '@rainlanguage/orderbook'; export const config: Config = { networks: { @@ -181,84 +176,61 @@ export const config: Config = { }, }; -export const configSource: ConfigSource = { - networks: { - network1: { - rpc: 'rpc-url', - 'chain-id': 14, - }, - network2: { - rpc: 'rpc-url', - 'chain-id': 137, - }, - }, - subgraphs: { - network1: 'some-url', - }, - orderbooks: { - network1: { - address: '0x123456', - network: 'network1', - subgraph: 'network1', - }, - }, - deployers: { - network1: { - address: '0xabcdef', - network: 'network1', - }, - }, - orders: { +test('pick deployments', () => { + const activeNetwork = 'network1'; + const result = pickDeployments(config, activeNetwork); + const expectedPickedDeployments: Dictionary = { sell: { - inputs: [], - outputs: [], - }, - buy: { - inputs: [], - outputs: [], - }, - }, - scenarios: { - network1: { - bindings: {}, - scenarios: { - buy: { - bindings: {}, + key: 'sell', + scenario: { + key: 'network1.sell', + bindings: {}, + deployer: { + key: 'network1', + address: '0xabcdef', + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, }, - sell: { - bindings: {}, + }, + order: { + key: 'sell', + inputs: [], + outputs: [], + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, }, }, }, - }, - charts: {}, - deployments: { - buy: { - scenario: 'network1.buy', - order: 'buy', - }, - sell: { - scenario: 'network1.sell', - order: 'sell', - }, - }, - accounts: { - name_one: 'address_one', - name_two: 'address_two', - }, -}; - -test('pick deployments', () => { - const activeNetwork = 'network1'; - const result = pickDeployments(configSource, config, activeNetwork); - const expectedPickedDeployments: Dictionary = { - sell: { - scenario: 'network1.sell', - order: 'sell', - }, buy: { - scenario: 'network1.buy', - order: 'buy', + key: 'buy', + scenario: { + key: 'network1.buy', + bindings: {}, + deployer: { + key: 'network1', + address: '0xabcdef', + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, + }, + }, + order: { + key: 'buy', + inputs: [], + outputs: [], + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, + }, }, }; @@ -267,8 +239,8 @@ test('pick deployments', () => { test('pick deployments when empty', () => { const activeNetwork = 'network2'; - const result = pickDeployments(configSource, config, activeNetwork); - const expectedPickedDeployments: Dictionary = {}; + const result = pickDeployments(config, activeNetwork); + const expectedPickedDeployments: Dictionary = {}; expect(result).toStrictEqual(expectedPickedDeployments); }); From 0bcef9985add98dec23a9df912d4ddd01faaeaee Mon Sep 17 00:00:00 2001 From: findolor Date: Fri, 18 Apr 2025 10:01:37 +0300 Subject: [PATCH 22/51] fix subgraph url settings --- .../src/lib/components/ListViewOrderbookFilters.svelte | 6 +++--- .../components/dropdown/DropdownActiveSubgraphs.svelte | 10 +++++----- .../src/lib/components/tables/OrdersListTable.svelte | 4 ++-- packages/ui-components/src/lib/types/appStores.ts | 8 ++++---- tauri-app/src/lib/stores/settings.ts | 8 ++++---- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte b/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte index 07a45dfc7..6a4d3ce57 100644 --- a/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte +++ b/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte @@ -8,16 +8,16 @@ import InputOrderHash from './input/InputOrderHash.svelte'; import CheckboxZeroBalanceVault from './CheckboxZeroBalanceVault.svelte'; import type { Readable, Writable } from 'svelte/store'; - import type { ConfigSource } from '@rainlanguage/orderbook'; + import type { Config, SubgraphCfg } from '@rainlanguage/orderbook'; import CheckboxMyItemsOnly from '$lib/components/CheckboxMyItemsOnly.svelte'; import { useAccount } from '$lib/providers/wallet/useAccount'; - export let settings: Writable; + export let settings: Writable; export let accounts: Readable> | undefined; export let hideZeroBalanceVaults: Writable; export let activeAccountsItems: Writable> | undefined; export let showMyItemsOnly: Writable; - export let activeSubgraphs: Writable>; + export let activeSubgraphs: Writable>; export let activeOrderStatus: Writable; export let orderHash: Writable; export let isVaultsPage: boolean; diff --git a/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte b/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte index c2189a298..714127da1 100644 --- a/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte +++ b/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte @@ -1,10 +1,10 @@
diff --git a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte index a36461324..bdcd8f104 100644 --- a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte +++ b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte @@ -10,7 +10,12 @@ import { DEFAULT_PAGE_SIZE, DEFAULT_REFRESH_INTERVAL } from '../../queries/constants'; import { vaultBalanceDisplay } from '../../utils/vault'; import { bigintStringToHex } from '../../utils/hex'; - import { type Config, type OrderbookCfg, type SubgraphCfg } from '@rainlanguage/orderbook'; + import { + type AccountCfg, + type Config, + type OrderbookCfg, + type SubgraphCfg + } from '@rainlanguage/orderbook'; import { type SgVault } from '@rainlanguage/orderbook'; import { QKEY_VAULTS } from '../../queries/keys'; import { @@ -23,7 +28,7 @@ import { useAccount } from '$lib/providers/wallet/useAccount'; export let activeOrderbook: Readable; - export let subgraphUrl: Readable; + export let subgraph: Readable; export let accounts: AppStoresInterface['accounts'] | undefined; export let activeAccountsItems: AppStoresInterface['activeAccountsItems'] | undefined; export let orderHash: Writable; @@ -34,7 +39,7 @@ export let activeNetworkRef: Writable; export let activeOrderbookRef: Writable; export let activeAccounts: Readable<{ - [k: string]: string; + [k: string]: AccountCfg; }>; export let handleDepositGenericModal: (() => void) | undefined = undefined; export let handleDepositModal: ((vault: SgVault, refetch: () => void) => void) | undefined = @@ -84,7 +89,7 @@ return lastPage.length === DEFAULT_PAGE_SIZE ? lastPageParam + 1 : undefined; }, refetchInterval: DEFAULT_REFRESH_INTERVAL, - enabled: !!$subgraphUrl + enabled: !!$subgraph }); const updateActiveNetworkAndOrderbook = (subgraphName: string) => { diff --git a/packages/ui-components/src/lib/types/appStores.ts b/packages/ui-components/src/lib/types/appStores.ts index 8ef7a3287..7d7bed7dd 100644 --- a/packages/ui-components/src/lib/types/appStores.ts +++ b/packages/ui-components/src/lib/types/appStores.ts @@ -1,9 +1,9 @@ import type { Readable, Writable } from 'svelte/store'; -import type { Config, OrderbookCfg, SubgraphCfg } from '@rainlanguage/orderbook'; +import type { AccountCfg, Config, OrderbookCfg, SubgraphCfg } from '@rainlanguage/orderbook'; export interface AppStoresInterface { settings: Writable; activeSubgraphs: Writable>; - accounts: Readable>; + accounts: Readable>; activeAccountsItems: Writable>; activeOrderStatus: Writable; orderHash: Writable; @@ -12,9 +12,9 @@ export interface AppStoresInterface { activeOrderbookRef: Writable; // New ones activeOrderbook: Readable; - subgraphUrl: Readable; + subgraph: Readable; activeAccounts: Readable<{ - [k: string]: string; + [k: string]: AccountCfg; }>; showMyItemsOnly: Writable; } diff --git a/packages/webapp/src/routes/+layout.ts b/packages/webapp/src/routes/+layout.ts index 2c7f93ec0..823c4dcaa 100644 --- a/packages/webapp/src/routes/+layout.ts +++ b/packages/webapp/src/routes/+layout.ts @@ -14,7 +14,7 @@ export const load = async ({ fetch }) => { ); const settingsYaml = await response.text(); - const settings = writable(parseSettings([settingsYaml])); + const settings = writable(parseSettings([settingsYaml])); const activeNetworkRef = writable(''); const activeOrderbookRef = writable(''); const activeOrderbook = derived( diff --git a/tauri-app/src/lib/services/order.ts b/tauri-app/src/lib/services/order.ts index 83dfd6694..a0032309d 100644 --- a/tauri-app/src/lib/services/order.ts +++ b/tauri-app/src/lib/services/order.ts @@ -26,9 +26,7 @@ export async function orderRemove(id: string) { derivation_index: get(ledgerWalletDerivationIndex), chain_id: get(chainId), }, - subgraphArgs: { - url: get(subgraph), - }, + subgraphArgs: get(subgraph)?.url, }); } diff --git a/tauri-app/src/lib/stores/order.ts b/tauri-app/src/lib/stores/order.ts index 2c43b39ae..4d87d2598 100644 --- a/tauri-app/src/lib/stores/order.ts +++ b/tauri-app/src/lib/stores/order.ts @@ -8,7 +8,7 @@ export const orderDetail = detailStore( 'orders.orderDetail', async (id: string) => { const loadedSubgraph = await subgraph.load(); - return invoke('order_detail', { id, subgraphArgs: loadedSubgraph }); + return invoke('order_detail', { id, subgraphArgs: { url: loadedSubgraph?.url } }); }, ); diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts index b2ff59f7c..af68ccd84 100644 --- a/tauri-app/src/lib/stores/settings.test.ts +++ b/tauri-app/src/lib/stores/settings.test.ts @@ -93,7 +93,10 @@ describe('Settings active accounts items', () => { const newSettings = { ...mockConfig, accounts: { - name_one: 'address_one', + name_one: { + key: 'name_one', + address: 'address_one', + }, }, }; @@ -110,8 +113,14 @@ describe('Settings active accounts items', () => { const newSettings = { ...mockConfig, accounts: { - name_one: 'address_one', - name_two: 'new_value', + name_one: { + key: 'name_one', + address: 'address_one', + }, + name_two: { + key: 'name_two', + address: 'new_value', + }, }, }; diff --git a/tauri-app/src/lib/stores/settings.ts b/tauri-app/src/lib/stores/settings.ts index 77dc0d464..2b4e5ea87 100644 --- a/tauri-app/src/lib/stores/settings.ts +++ b/tauri-app/src/lib/stores/settings.ts @@ -168,12 +168,14 @@ settings.subscribe(async () => { } else { const currentActiveAccounts = get(activeAccountsItems); const updatedActiveAccounts = Object.fromEntries( - Object.entries($settings.accounts ?? {}).filter(([key, value]) => { - if (key in currentActiveAccounts) { - return currentActiveAccounts[key] === value; - } - return false; - }), + Object.entries($settings.accounts ?? {}) + .filter(([key, value]) => { + if (key in currentActiveAccounts) { + return currentActiveAccounts[key] === value.address; + } + return false; + }) + .map(([key, value]) => [key, value.address]), ); activeAccountsItems.set(updatedActiveAccounts); } diff --git a/tauri-app/src/routes/vaults/+page.svelte b/tauri-app/src/routes/vaults/+page.svelte index 3257ccb55..4afc6fc3a 100644 --- a/tauri-app/src/routes/vaults/+page.svelte +++ b/tauri-app/src/routes/vaults/+page.svelte @@ -5,7 +5,7 @@ import { activeOrderbook, - subgraphUrl, + subgraph, orderHash, accounts, activeAccountsItems, @@ -42,7 +42,7 @@ Date: Thu, 24 Apr 2025 11:44:45 +0300 Subject: [PATCH 33/51] small changes --- packages/ui-components/src/lib/types/appStores.ts | 2 +- tauri-app/src/lib/services/order.ts | 4 +++- tauri-app/src/lib/stores/settings.test.ts | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui-components/src/lib/types/appStores.ts b/packages/ui-components/src/lib/types/appStores.ts index 7d7bed7dd..2f27db288 100644 --- a/packages/ui-components/src/lib/types/appStores.ts +++ b/packages/ui-components/src/lib/types/appStores.ts @@ -1,7 +1,7 @@ import type { Readable, Writable } from 'svelte/store'; import type { AccountCfg, Config, OrderbookCfg, SubgraphCfg } from '@rainlanguage/orderbook'; export interface AppStoresInterface { - settings: Writable; + settings: Writable; activeSubgraphs: Writable>; accounts: Readable>; activeAccountsItems: Writable>; diff --git a/tauri-app/src/lib/services/order.ts b/tauri-app/src/lib/services/order.ts index a0032309d..d510b8db4 100644 --- a/tauri-app/src/lib/services/order.ts +++ b/tauri-app/src/lib/services/order.ts @@ -26,7 +26,9 @@ export async function orderRemove(id: string) { derivation_index: get(ledgerWalletDerivationIndex), chain_id: get(chainId), }, - subgraphArgs: get(subgraph)?.url, + subgraphArgs: { + url: get(subgraph)?.url, + }, }); } diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts index af68ccd84..73dd68970 100644 --- a/tauri-app/src/lib/stores/settings.test.ts +++ b/tauri-app/src/lib/stores/settings.test.ts @@ -1,8 +1,6 @@ import { expect, test, beforeEach, describe } from 'vitest'; import { settings, activeAccountsItems, activeSubgraphs } from './settings'; import { get } from 'svelte/store'; - -// Import the ConfigSource type import type { Config, NetworkCfg, SubgraphCfg } from '@rainlanguage/orderbook'; // Define the mock directly in the tests From 711a28dbb5032978a3dd4bd0d82ba0c92d559c63 Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 24 Apr 2025 11:55:10 +0300 Subject: [PATCH 34/51] small update --- tauri-app/src/lib/stores/settings.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts index 73dd68970..ab3a2cfd0 100644 --- a/tauri-app/src/lib/stores/settings.test.ts +++ b/tauri-app/src/lib/stores/settings.test.ts @@ -133,7 +133,10 @@ describe('Settings active accounts items', () => { const newSettings = { ...mockConfig, subgraphs: { - mainnet: 'new value', + mainnet: { + key: 'mainnet', + url: 'new value', + }, }, }; @@ -146,7 +149,10 @@ describe('Settings active accounts items', () => { const newSettings = { ...mockConfig, subgraphs: { - testnet: 'testnet', + testnet: { + key: 'testnet', + url: 'testnet', + }, }, }; From 0411677a0d715aea93fc6404e8547843379b8d38 Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 24 Apr 2025 12:54:40 +0300 Subject: [PATCH 35/51] ci fixes --- packages/webapp/src/routes/vaults/+page.svelte | 4 ++-- tauri-app/src/lib/stores/settings.test.ts | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/webapp/src/routes/vaults/+page.svelte b/packages/webapp/src/routes/vaults/+page.svelte index e61823d8b..239b79e52 100644 --- a/packages/webapp/src/routes/vaults/+page.svelte +++ b/packages/webapp/src/routes/vaults/+page.svelte @@ -7,7 +7,7 @@ const { activeOrderbook, - subgraphUrl, + subgraph, orderHash, activeSubgraphs, settings, @@ -56,7 +56,7 @@ { settings.set(newSettings as unknown as Config); - expect(get(activeSubgraphs)).toEqual({}); + expect(get(activeSubgraphs)).toEqual({ + mainnet: { + key: 'mainnet', + url: 'new value', + }, + }); }); test('should update active subgraphs when subgraph removed', () => { From de66421c06d56afc0b646002c7863fd736666081 Mon Sep 17 00:00:00 2001 From: findolor Date: Thu, 24 Apr 2025 13:56:43 +0300 Subject: [PATCH 36/51] update settings tests --- tauri-app/src/lib/stores/settings.test.ts | 4 ++-- tauri-app/src/lib/stores/settings.ts | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts index 7b19c5a7c..1b9863461 100644 --- a/tauri-app/src/lib/stores/settings.test.ts +++ b/tauri-app/src/lib/stores/settings.test.ts @@ -1,5 +1,5 @@ import { expect, test, beforeEach, describe } from 'vitest'; -import { settings, activeAccountsItems, activeSubgraphs } from './settings'; +import { settings, activeAccountsItems, activeSubgraphs, EMPTY_SETTINGS } from './settings'; import { get } from 'svelte/store'; import type { Config, NetworkCfg, SubgraphCfg } from '@rainlanguage/orderbook'; @@ -55,7 +55,7 @@ describe('Settings active accounts items', () => { // Reset store values before each test to prevent state leakage beforeEach(() => { // Reset all store values - settings.set(undefined); + settings.set(EMPTY_SETTINGS); activeAccountsItems.set({}); activeSubgraphs.set({}); diff --git a/tauri-app/src/lib/stores/settings.ts b/tauri-app/src/lib/stores/settings.ts index 2b4e5ea87..7b8edaf2b 100644 --- a/tauri-app/src/lib/stores/settings.ts +++ b/tauri-app/src/lib/stores/settings.ts @@ -10,6 +10,20 @@ import { type Config, type OrderbookCfg, type SubgraphCfg } from '@rainlanguage/ import { getBlockNumberFromRpc } from '$lib/services/chain'; import { pickBy } from 'lodash'; +export const EMPTY_SETTINGS: Config = { + networks: {}, + subgraphs: {}, + metaboards: {}, + orderbooks: {}, + accounts: {}, + tokens: {}, + deployers: {}, + orders: {}, + scenarios: {}, + charts: {}, + deployments: {}, +}; + // general export const settingsText = cachedWritableStore( 'settingsText', @@ -22,15 +36,15 @@ export const settingsFile = textFileStore( ['yml', 'yaml'], get(settingsText), ); -export const settings = cachedWritableStore( +export const settings = cachedWritableStore( 'settings', - undefined, + EMPTY_SETTINGS, (value) => JSON.stringify(value), (str) => { try { return JSON.parse(str) as Config; } catch { - return undefined; + return EMPTY_SETTINGS; } }, ); From 3ff77da477654713da36480723ce9e9edc2b1f87 Mon Sep 17 00:00:00 2001 From: findolor Date: Mon, 28 Apr 2025 08:22:12 +0300 Subject: [PATCH 37/51] remove web-sys library --- Cargo.lock | 2 -- Cargo.toml | 1 - crates/common/Cargo.toml | 1 - crates/settings/Cargo.toml | 1 - tauri-app/src-tauri/Cargo.lock | 2 -- 5 files changed, 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34b20cd4e..3738ca8b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6342,7 +6342,6 @@ dependencies = [ "tokio", "url", "wasm-bindgen-utils 0.0.7", - "web-sys", ] [[package]] @@ -6420,7 +6419,6 @@ dependencies = [ "tracing", "url", "wasm-bindgen-utils 0.0.7", - "web-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3b3c74f81..84fbbb84a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,6 @@ portpicker = "0.1.1" rain-erc = { git = "https://github.com/rainlanguage/rain.erc", rev = "0106e645ebd49334addc698c5aad9a85370eb54d" } rain-error-decoding = { git = "https://github.com/rainlanguage/rain.error", rev = "72d9577fdaf7135113847027ba951f9a43b41827" } wasm-bindgen-utils = "0.0.7" -web-sys = { version = "0.3.77", features = [ "console" ] } [workspace.dependencies.rain_orderbook_bindings] path = "crates/bindings" diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index f4f6d8253..2cb8a73d5 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -41,7 +41,6 @@ chrono = { workspace = true } futures = { workspace = true } rain-error-decoding = { workspace = true } rain-interpreter-eval = { workspace = true } -web-sys = { workspace = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 027e439bb..48db05421 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -20,7 +20,6 @@ strict-yaml-rust = { workspace = true } alloy = { workspace = true, features = ["serde", "rand"] } reqwest = { workspace = true } rain_orderbook_bindings = { workspace = true } -web-sys = { workspace = true } [target.'cfg(target_family = "wasm")'.dependencies] wasm-bindgen-utils = { workspace = true } diff --git a/tauri-app/src-tauri/Cargo.lock b/tauri-app/src-tauri/Cargo.lock index 36fa2ca47..897f41618 100644 --- a/tauri-app/src-tauri/Cargo.lock +++ b/tauri-app/src-tauri/Cargo.lock @@ -8278,7 +8278,6 @@ dependencies = [ "thiserror", "url", "wasm-bindgen-utils", - "web-sys", ] [[package]] @@ -8325,7 +8324,6 @@ dependencies = [ "tracing", "url", "wasm-bindgen-utils", - "web-sys", ] [[package]] From a98ba3ca9bbe4f6956a9fc9cce3d7e3707cbd663 Mon Sep 17 00:00:00 2001 From: findolor Date: Mon, 28 Apr 2025 09:41:39 +0300 Subject: [PATCH 38/51] update things to do --- crates/cli/src/commands/chart/mod.rs | 2 +- crates/common/src/frontmatter.rs | 2 +- crates/common/src/fuzz/impls.rs | 12 +- crates/common/src/unit_tests/mod.rs | 3 +- crates/js_api/src/config.rs | 14 +- crates/settings/src/config.rs | 410 ++++++++---------- crates/settings/src/test.rs | 161 +++++++ packages/webapp/src/routes/+layout.ts | 106 ++--- packages/webapp/test-setup.ts | 2 +- tauri-app/src-tauri/src/commands/config.rs | 4 +- .../src/lib/components/ModalExecute.svelte | 2 +- .../src/lib/components/ModalExecute.test.ts | 16 +- tauri-app/src/lib/services/pickConfig.ts | 14 +- tauri-app/src/lib/stores/settings.test.ts | 147 ++++--- tauri-app/src/lib/stores/settings.ts | 79 ++-- tauri-app/src/lib/stores/walletconnect.ts | 4 +- tauri-app/src/tests/pickConfig.test.ts | 224 +++++----- 17 files changed, 675 insertions(+), 527 deletions(-) diff --git a/crates/cli/src/commands/chart/mod.rs b/crates/cli/src/commands/chart/mod.rs index 64ba7201f..a0b4dae9b 100644 --- a/crates/cli/src/commands/chart/mod.rs +++ b/crates/cli/src/commands/chart/mod.rs @@ -22,7 +22,7 @@ impl Execute for Chart { async fn execute(&self) -> Result<()> { let dotrain = read_to_string(self.dotrain_file.clone()).map_err(|e| anyhow!(e))?; let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); - let config = Config::try_from_settings(vec![frontmatter.to_string()], false)?; + let config = Config::try_from_yaml(vec![frontmatter.to_string()], false)?; let fuzzer = FuzzRunner::new(&dotrain, config, None).await; let chart_data = fuzzer.make_chart_data().await?; diff --git a/crates/common/src/frontmatter.rs b/crates/common/src/frontmatter.rs index dc4355ac3..2c2552c7e 100644 --- a/crates/common/src/frontmatter.rs +++ b/crates/common/src/frontmatter.rs @@ -5,5 +5,5 @@ use rain_orderbook_app_settings::{Config, ParseConfigError}; /// Parse dotrain frontmatter and merges it with top Config if given pub fn parse_frontmatter(dotrain: String, validate: bool) -> Result { let frontmatter = RainDocument::get_front_matter(dotrain.as_str()).unwrap_or(""); - Config::try_from_settings(vec![frontmatter.to_string()], validate) + Config::try_from_yaml(vec![frontmatter.to_string()], validate) } diff --git a/crates/common/src/fuzz/impls.rs b/crates/common/src/fuzz/impls.rs index 933280fb4..1e62d7aca 100644 --- a/crates/common/src/fuzz/impls.rs +++ b/crates/common/src/fuzz/impls.rs @@ -325,7 +325,7 @@ b: fuzzed; ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); let config = - Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()], false) + Config::try_from_yaml(vec![frontmatter.to_string(), SETTINGS.to_string()], false) .unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -378,7 +378,7 @@ _: block-number(); ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); let config = - Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()], false) + Config::try_from_yaml(vec![frontmatter.to_string(), SETTINGS.to_string()], false) .unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -439,7 +439,7 @@ d: 4; ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); let config = - Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()], false) + Config::try_from_yaml(vec![frontmatter.to_string(), SETTINGS.to_string()], false) .unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -496,7 +496,7 @@ _: context<4 4>(); ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); let config = - Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()], false) + Config::try_from_yaml(vec![frontmatter.to_string(), SETTINGS.to_string()], false) .unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -542,7 +542,7 @@ _: context<50 50>(); ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); let config = - Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()], false) + Config::try_from_yaml(vec![frontmatter.to_string(), SETTINGS.to_string()], false) .unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; @@ -583,7 +583,7 @@ _: context<1 0>(); ); let frontmatter = RainDocument::get_front_matter(&dotrain).unwrap(); let config = - Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()], false) + Config::try_from_yaml(vec![frontmatter.to_string(), SETTINGS.to_string()], false) .unwrap(); let mut runner = FuzzRunner::new(&dotrain, config, None).await; diff --git a/crates/common/src/unit_tests/mod.rs b/crates/common/src/unit_tests/mod.rs index 630ff00c1..ec4cfdd1c 100644 --- a/crates/common/src/unit_tests/mod.rs +++ b/crates/common/src/unit_tests/mod.rs @@ -404,8 +404,7 @@ deployments: fn get_main_config(dotrain: &str) -> Config { let frontmatter = RainDocument::get_front_matter(dotrain).unwrap(); - Config::try_from_settings(vec![frontmatter.to_string(), SETTINGS.to_string()], false) - .unwrap() + Config::try_from_yaml(vec![frontmatter.to_string(), SETTINGS.to_string()], false).unwrap() } fn get_test_config(test_dotrain: &str) -> TestConfig { diff --git a/crates/js_api/src/config.rs b/crates/js_api/src/config.rs index 60ab702cd..ff52c201f 100644 --- a/crates/js_api/src/config.rs +++ b/crates/js_api/src/config.rs @@ -1,12 +1,12 @@ use rain_orderbook_app_settings::{config::Config, ParseConfigError}; use wasm_bindgen_utils::prelude::*; -#[wasm_bindgen(js_name = "parseSettings", unchecked_return_type = "Config")] -pub fn parse_settings( - settings: Vec, +#[wasm_bindgen(js_name = "parseYaml", unchecked_return_type = "Config")] +pub fn parse_yaml( + yaml_list: Vec, validate: Option, ) -> Result { - let config = Config::try_from_settings(settings, validate.unwrap_or(false))?; + let config = Config::try_from_yaml(yaml_list, validate.unwrap_or(false))?; Ok(to_js_value(&config)?) } @@ -173,13 +173,13 @@ mod tests { "#; #[wasm_bindgen_test] - fn test_parse_settings() { - let config = Config::try_from_settings( + fn test_parse_yaml() { + let config = Config::try_from_yaml( vec![ORDERBOOK_YAML.to_string(), DOTRAIN_YAML.to_string()], false, ) .unwrap(); - let js_value = parse_settings( + let js_value = parse_yaml( vec![ORDERBOOK_YAML.to_string(), DOTRAIN_YAML.to_string()], None, ) diff --git a/crates/settings/src/config.rs b/crates/settings/src/config.rs index aced86191..dd1bcee3f 100644 --- a/crates/settings/src/config.rs +++ b/crates/settings/src/config.rs @@ -13,9 +13,19 @@ use url::Url; use wasm_bindgen_utils::{impl_wasm_traits, prelude::*, serialize_hashmap_as_object}; #[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] #[cfg_attr(target_family = "wasm", derive(Tsify))] pub struct Config { + dotrain_order: DotrainOrderConfig, + orderbook: OrderbookConfig, +} +#[cfg(target_family = "wasm")] +impl_wasm_traits!(Config); + +#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(target_family = "wasm", derive(Tsify))] +pub struct OrderbookConfig { #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), @@ -52,24 +62,36 @@ pub struct Config { tsify(type = "Record") )] deployers: HashMap>, + #[cfg_attr(target_family = "wasm", tsify(optional))] + sentry: Option, + #[cfg_attr(target_family = "wasm", tsify(optional))] + raindex_version: Option, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), - tsify(type = "Record") + tsify(type = "Record", optional) )] - orders: HashMap>, + accounts: HashMap>, +} +#[cfg(target_family = "wasm")] +impl_wasm_traits!(OrderbookConfig); + +#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(target_family = "wasm", derive(Tsify))] +pub struct DotrainOrderConfig { #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), - tsify(type = "Record") + tsify(type = "Record") )] - scenarios: HashMap>, + orders: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), - tsify(type = "Record") + tsify(type = "Record") )] - charts: HashMap>, + scenarios: HashMap>, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), @@ -77,20 +99,16 @@ pub struct Config { )] deployments: HashMap>, #[cfg_attr(target_family = "wasm", tsify(optional))] - sentry: Option, - #[cfg_attr(target_family = "wasm", tsify(optional))] - raindex_version: Option, + gui: Option, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), - tsify(type = "Record", optional) + tsify(type = "Record") )] - accounts: HashMap>, - #[cfg_attr(target_family = "wasm", tsify(optional))] - gui: Option, + charts: HashMap>, } #[cfg(target_family = "wasm")] -impl_wasm_traits!(Config); +impl_wasm_traits!(DotrainOrderConfig); #[derive(Error, Debug)] pub enum ParseConfigError { @@ -147,12 +165,9 @@ impl From for JsValue { } impl Config { - pub fn try_from_settings( - settings: Vec, - validate: bool, - ) -> Result { - let dotrain_yaml = DotrainYaml::new(settings.clone(), validate)?; - let orderbook_yaml = OrderbookYaml::new(settings, validate)?; + pub fn try_from_yaml(yaml: Vec, validate: bool) -> Result { + let dotrain_yaml = DotrainYaml::new(yaml.clone(), validate)?; + let orderbook_yaml = OrderbookYaml::new(yaml, validate)?; let networks = orderbook_yaml .get_networks() @@ -224,132 +239,158 @@ impl Config { .map(|(k, v)| (k, Arc::new(v))) .collect::>(); - let config = Config { + let orderbook_config = OrderbookConfig { networks, subgraphs, metaboards, orderbooks, tokens, deployers, - orders, - scenarios, - charts, - deployments, sentry, raindex_version, accounts, + }; + let dotrain_order_config = DotrainOrderConfig { + orders, + scenarios, + deployments, gui, + charts, + }; + + let config = Config { + dotrain_order: dotrain_order_config, + orderbook: orderbook_config, }; Ok(config) } + pub fn get_orderbook_config(&self) -> OrderbookConfig { + self.orderbook.clone() + } + + pub fn get_dotrain_order_config(&self) -> DotrainOrderConfig { + self.dotrain_order.clone() + } + pub fn get_networks(&self) -> &HashMap> { - &self.networks + &self.orderbook.networks } pub fn get_network(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.networks + self.orderbook + .networks .get(key) .ok_or(ParseConfigError::NetworkNotFound(key.to_string())) } pub fn get_subgraphs(&self) -> &HashMap> { - &self.subgraphs + &self.orderbook.subgraphs } pub fn get_subgraph(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.subgraphs + self.orderbook + .subgraphs .get(key) .ok_or(ParseConfigError::SubgraphNotFound(key.to_string())) } pub fn get_metaboards(&self) -> &HashMap> { - &self.metaboards + &self.orderbook.metaboards } pub fn get_metaboard(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.metaboards + self.orderbook + .metaboards .get(key) .ok_or(ParseConfigError::MetaboardNotFound(key.to_string())) } pub fn get_orderbooks(&self) -> &HashMap> { - &self.orderbooks + &self.orderbook.orderbooks } pub fn get_orderbook(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.orderbooks + self.orderbook + .orderbooks .get(key) .ok_or(ParseConfigError::OrderbookNotFound(key.to_string())) } pub fn get_tokens(&self) -> &HashMap> { - &self.tokens + &self.orderbook.tokens } pub fn get_token(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.tokens + self.orderbook + .tokens .get(key) .ok_or(ParseConfigError::TokenNotFound(key.to_string())) } pub fn get_deployers(&self) -> &HashMap> { - &self.deployers + &self.orderbook.deployers } pub fn get_deployer(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.deployers + self.orderbook + .deployers .get(key) .ok_or(ParseConfigError::DeployerNotFound(key.to_string())) } pub fn get_orders(&self) -> &HashMap> { - &self.orders + &self.dotrain_order.orders } pub fn get_order(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.orders + self.dotrain_order + .orders .get(key) .ok_or(ParseConfigError::OrderNotFound(key.to_string())) } pub fn get_scenarios(&self) -> &HashMap> { - &self.scenarios + &self.dotrain_order.scenarios } pub fn get_scenario(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.scenarios + self.dotrain_order + .scenarios .get(key) .ok_or(ParseConfigError::ScenarioNotFound(key.to_string())) } pub fn get_deployments(&self) -> &HashMap> { - &self.deployments + &self.dotrain_order.deployments } pub fn get_deployment(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.deployments + self.dotrain_order + .deployments .get(key) .ok_or(ParseConfigError::DeploymentNotFound(key.to_string())) } pub fn get_charts(&self) -> &HashMap> { - &self.charts + &self.dotrain_order.charts } pub fn get_chart(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.charts + self.dotrain_order + .charts .get(key) .ok_or(ParseConfigError::ChartNotFound(key.to_string())) } pub fn get_gui(&self) -> &Option { - &self.gui + &self.dotrain_order.gui } pub fn get_sentry(&self) -> &Option { - &self.sentry + &self.orderbook.sentry } pub fn get_raindex_version(&self) -> &Option { - &self.raindex_version + &self.orderbook.raindex_version } pub fn get_accounts(&self) -> &HashMap> { - &self.accounts + &self.orderbook.accounts } pub fn get_account(&self, key: &str) -> Result<&Arc, ParseConfigError> { - self.accounts + self.orderbook + .accounts .get(key) .ok_or(ParseConfigError::AccountNotFound(key.to_string())) } @@ -357,7 +398,9 @@ impl Config { #[cfg(test)] mod tests { + use super::Config; use crate::{ + test::{MOCK_DOTRAIN_YAML, MOCK_ORDERBOOK_YAML}, BinXTransformCfg, DotOptionsCfg, HexBinTransformCfg, LineOptionsCfg, MarkCfg, RectYOptionsCfg, TransformCfg, }; @@ -365,191 +408,46 @@ mod tests { use std::str::FromStr; use url::Url; - use super::Config; + fn setup_config() -> Config { + Config::try_from_yaml( + vec![ + MOCK_ORDERBOOK_YAML.to_string(), + MOCK_DOTRAIN_YAML.to_string(), + ], + false, + ) + .unwrap() + } - const ORDERBOOK_YAML: &str = r#" - raindex-version: 0.1.0 - networks: - mainnet: - rpc: https://mainnet.infura.io - chain-id: 1 - testnet: - rpc: https://testnet.infura.io - chain-id: 1337 - subgraphs: - mainnet: https://mainnet-subgraph.com - testnet: https://testnet-subgraph.com - metaboards: - mainnet: https://mainnet-metaboard.com - testnet: https://testnet-metaboard.com - orderbooks: - mainnet: - address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - network: mainnet - subgraph: mainnet - testnet: - address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - network: testnet - subgraph: testnet - tokens: - token1: - network: mainnet - address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - decimals: 18 - label: Wrapped Ether - symbol: WETH - token2: - network: mainnet - address: 0x0000000000000000000000000000000000000002 - decimals: 6 - label: USD Coin - symbol: USDC - deployers: - scenario1: - address: 0x0000000000000000000000000000000000000002 - network: mainnet - deployer2: - address: 0x0000000000000000000000000000000000000003 - network: testnet - sentry: true - accounts: - account1: 0x0000000000000000000000000000000000000001 - account2: 0x0000000000000000000000000000000000000002 - "#; - const DOTRAIN_YAML: &str = r#" - orders: - order1: - deployer: scenario1 - orderbook: mainnet - inputs: - - token: token1 - vault-id: 1 - outputs: - - token: token2 - vault-id: 2 - scenarios: - scenario1: - bindings: - key1: value1 - scenarios: - scenario2: - bindings: - key2: value2 - runs: 10 - deployments: - deployment1: - order: order1 - scenario: scenario1.scenario2 - deployment2: - order: order1 - scenario: scenario1 - gui: - name: Test gui - description: Test description - short-description: Test short description - deployments: - deployment1: - name: Test deployment - description: Test description - deposits: - - token: token1 - presets: - - 100 - - 2000 - fields: - - binding: key1 - name: Binding test - presets: - - value: value2 - select-tokens: - - key: token2 - name: Test token - description: Test description - charts: - chart1: - scenario: scenario1.scenario2 - plots: - plot1: - title: Test title - subtitle: Test subtitle - marks: - - type: dot - options: - x: 1 - y: 2 - r: 3 - fill: red - stroke: blue - transform: - type: hexbin - content: - outputs: - x: 1 - y: 2 - r: 3 - z: 4 - stroke: green - fill: blue - options: - x: 1 - y: 2 - bin-width: 10 - - type: line - options: - transform: - type: binx - content: - outputs: - x: 1 - options: - thresholds: 10 - - type: recty - options: - x0: 1 - x1: 2 - y0: 3 - y1: 4 - x: - label: Test x label - anchor: start - label-anchor: start - label-arrow: none - y: - label: Test y label - anchor: start - label-anchor: start - label-arrow: none - margin: 10 - margin-left: 20 - margin-right: 30 - margin-top: 40 - margin-bottom: 50 - inset: 60 - "#; + #[test] + fn test_orderbook_config() { + let config = setup_config(); + let orderbook_config = config.get_orderbook_config(); + assert_eq!(orderbook_config.networks.len(), 2); + assert_eq!(orderbook_config.subgraphs.len(), 2); + assert_eq!(orderbook_config.metaboards.len(), 2); + assert_eq!(orderbook_config.orderbooks.len(), 2); + assert_eq!(orderbook_config.tokens.len(), 2); + assert_eq!(orderbook_config.deployers.len(), 2); + assert_eq!(orderbook_config.accounts.len(), 2); + assert!(orderbook_config.sentry.is_some()); + assert!(orderbook_config.raindex_version.is_some()); + } #[test] - fn test_try_from_settings() { - let config = Config::try_from_settings( - vec![ORDERBOOK_YAML.to_string(), DOTRAIN_YAML.to_string()], - false, - ) - .unwrap(); - - assert_eq!(config.get_networks().len(), 2); - assert_eq!(config.get_subgraphs().len(), 2); - assert_eq!(config.get_metaboards().len(), 2); - assert_eq!(config.get_orderbooks().len(), 2); - assert_eq!(config.get_tokens().len(), 2); - assert_eq!(config.get_deployers().len(), 2); - assert_eq!(config.get_orders().len(), 1); - assert_eq!(config.get_scenarios().len(), 2); - assert_eq!(config.get_deployments().len(), 2); - assert_eq!(config.get_charts().len(), 1); - assert!(config.get_gui().is_some()); - assert!(config.get_sentry().is_some()); - assert!(config.get_raindex_version().is_some()); - assert_eq!(config.get_accounts().len(), 2); + fn test_dotrain_order_config() { + let config = setup_config(); + let dotrain_order_config = config.get_dotrain_order_config(); + assert_eq!(dotrain_order_config.orders.len(), 1); + assert_eq!(dotrain_order_config.scenarios.len(), 2); + assert_eq!(dotrain_order_config.deployments.len(), 2); + assert!(dotrain_order_config.gui.is_some()); + assert_eq!(dotrain_order_config.charts.len(), 1); + } + #[test] + fn test_networks() { + let config = setup_config(); let mainnet_network = config.get_network("mainnet").unwrap(); assert_eq!(mainnet_network.key, "mainnet"); assert_eq!( @@ -570,7 +468,11 @@ mod tests { assert!(testnet_network.label.is_none()); assert!(testnet_network.network_id.is_none()); assert!(testnet_network.currency.is_none()); + } + #[test] + fn test_subgraphs() { + let config = setup_config(); let mainnet_subgraph = config.get_subgraph("mainnet").unwrap(); assert_eq!(mainnet_subgraph.key, "mainnet"); assert_eq!( @@ -583,7 +485,11 @@ mod tests { testnet_subgraph.url, Url::parse("https://testnet-subgraph.com").unwrap() ); + } + #[test] + fn test_metaboards() { + let config = setup_config(); let mainnet_metaboard = config.get_metaboard("mainnet").unwrap(); assert_eq!( **mainnet_metaboard, @@ -594,7 +500,11 @@ mod tests { **testnet_metaboard, Url::parse("https://testnet-metaboard.com").unwrap() ); + } + #[test] + fn test_orderbooks() { + let config = setup_config(); let mainnet_orderbook = config.get_orderbook("mainnet").unwrap(); assert_eq!(mainnet_orderbook.key, "mainnet"); assert_eq!( @@ -613,7 +523,11 @@ mod tests { assert_eq!(testnet_orderbook.network.key, "testnet"); assert_eq!(testnet_orderbook.subgraph.key, "testnet"); assert!(testnet_orderbook.label.is_none()); + } + #[test] + fn test_tokens() { + let config = setup_config(); let token1 = config.get_token("token1").unwrap(); assert_eq!(token1.key, "token1"); assert_eq!(token1.network.key, "mainnet"); @@ -634,7 +548,11 @@ mod tests { assert_eq!(token2.decimals, Some(6)); assert_eq!(token2.label, Some("USD Coin".to_string())); assert_eq!(token2.symbol, Some("USDC".to_string())); + } + #[test] + fn test_deployers() { + let config = setup_config(); let deployer_scenario1 = config.get_deployer("scenario1").unwrap(); assert_eq!(deployer_scenario1.key, "scenario1"); assert_eq!( @@ -649,7 +567,11 @@ mod tests { Address::from_str("0x0000000000000000000000000000000000000003").unwrap() ); assert_eq!(deployer2.network.key, "testnet"); + } + #[test] + fn test_orders() { + let config = setup_config(); let order1 = config.get_order("order1").unwrap(); assert_eq!(order1.key, "order1"); assert_eq!(order1.network.key, "mainnet"); @@ -663,7 +585,11 @@ mod tests { let order1_output = &order1.outputs[0]; assert_eq!(order1_output.token.as_ref().unwrap().key, "token2"); assert_eq!(order1_output.vault_id, Some(U256::from(2))); + } + #[test] + fn test_scenarios() { + let config = setup_config(); let scenario1 = config.get_scenario("scenario1").unwrap(); assert_eq!(scenario1.key, "scenario1"); assert_eq!(scenario1.bindings.len(), 1); @@ -679,7 +605,11 @@ mod tests { assert_eq!(scenario1_scenario2.runs.unwrap(), 10); assert!(scenario1_scenario2.blocks.is_none()); assert_eq!(scenario1_scenario2.deployer.key, "scenario1"); + } + #[test] + fn test_deployments() { + let config = setup_config(); let deployment1 = config.get_deployment("deployment1").unwrap(); assert_eq!(deployment1.key, "deployment1"); assert_eq!(deployment1.order.key, "order1"); @@ -688,7 +618,11 @@ mod tests { assert_eq!(deployment2.key, "deployment2"); assert_eq!(deployment2.order.key, "order1"); assert_eq!(deployment2.scenario.key, "scenario1"); + } + #[test] + fn test_charts() { + let config = setup_config(); let chart1 = config.get_chart("chart1").unwrap(); assert_eq!(chart1.key, "chart1"); assert_eq!(chart1.scenario.key, "scenario1.scenario2"); @@ -787,7 +721,11 @@ mod tests { assert_eq!(plot1.margin_top, Some(40)); assert_eq!(plot1.margin_bottom, Some(50)); assert_eq!(plot1.inset, Some(60)); + } + #[test] + fn test_gui() { + let config = setup_config(); let gui = config.get_gui().as_ref().unwrap(); assert_eq!(gui.name, "Test gui"); assert_eq!(gui.description, "Test description"); @@ -826,12 +764,20 @@ mod tests { select_token1.description, Some("Test description".to_string()) ); + } + #[test] + fn test_sentry_raindex_version() { + let config = setup_config(); let sentry = config.get_sentry().unwrap(); assert!(sentry); let raindex_version = config.get_raindex_version().as_ref().unwrap(); assert_eq!(raindex_version, "0.1.0"); + } + #[test] + fn test_accounts() { + let config = setup_config(); let account1 = config.get_account("account1").unwrap(); assert_eq!(account1.key, "account1"); assert_eq!( diff --git a/crates/settings/src/test.rs b/crates/settings/src/test.rs index 8305976a7..23db71a31 100644 --- a/crates/settings/src/test.rs +++ b/crates/settings/src/test.rs @@ -89,3 +89,164 @@ pub fn mock_subgraph() -> Arc { url: "https://subgraph.com".parse().unwrap(), }) } + +pub const MOCK_ORDERBOOK_YAML: &str = r#" +raindex-version: 0.1.0 +networks: + mainnet: + rpc: https://mainnet.infura.io + chain-id: 1 + testnet: + rpc: https://testnet.infura.io + chain-id: 1337 +subgraphs: + mainnet: https://mainnet-subgraph.com + testnet: https://testnet-subgraph.com +metaboards: + mainnet: https://mainnet-metaboard.com + testnet: https://testnet-metaboard.com +orderbooks: + mainnet: + address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + network: mainnet + subgraph: mainnet + testnet: + address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + network: testnet + subgraph: testnet +tokens: + token1: + network: mainnet + address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + decimals: 18 + label: Wrapped Ether + symbol: WETH + token2: + network: mainnet + address: 0x0000000000000000000000000000000000000002 + decimals: 6 + label: USD Coin + symbol: USDC +deployers: + scenario1: + address: 0x0000000000000000000000000000000000000002 + network: mainnet + deployer2: + address: 0x0000000000000000000000000000000000000003 + network: testnet +sentry: true +accounts: + account1: 0x0000000000000000000000000000000000000001 + account2: 0x0000000000000000000000000000000000000002 +"#; + +pub const MOCK_DOTRAIN_YAML: &str = r#" +orders: + order1: + deployer: scenario1 + orderbook: mainnet + inputs: + - token: token1 + vault-id: 1 + outputs: + - token: token2 + vault-id: 2 +scenarios: + scenario1: + bindings: + key1: value1 + scenarios: + scenario2: + bindings: + key2: value2 + runs: 10 +deployments: + deployment1: + order: order1 + scenario: scenario1.scenario2 + deployment2: + order: order1 + scenario: scenario1 +gui: + name: Test gui + description: Test description + short-description: Test short description + deployments: + deployment1: + name: Test deployment + description: Test description + deposits: + - token: token1 + presets: + - 100 + - 2000 + fields: + - binding: key1 + name: Binding test + presets: + - value: value2 + select-tokens: + - key: token2 + name: Test token + description: Test description +charts: + chart1: + scenario: scenario1.scenario2 + plots: + plot1: + title: Test title + subtitle: Test subtitle + marks: + - type: dot + options: + x: 1 + y: 2 + r: 3 + fill: red + stroke: blue + transform: + type: hexbin + content: + outputs: + x: 1 + y: 2 + r: 3 + z: 4 + stroke: green + fill: blue + options: + x: 1 + y: 2 + bin-width: 10 + - type: line + options: + transform: + type: binx + content: + outputs: + x: 1 + options: + thresholds: 10 + - type: recty + options: + x0: 1 + x1: 2 + y0: 3 + y1: 4 + x: + label: Test x label + anchor: start + label-anchor: start + label-arrow: none + y: + label: Test y label + anchor: start + label-anchor: start + label-arrow: none + margin: 10 + margin-left: 20 + margin-right: 30 + margin-top: 40 + margin-bottom: 50 + inset: 60 +"#; diff --git a/packages/webapp/src/routes/+layout.ts b/packages/webapp/src/routes/+layout.ts index 823c4dcaa..f2ec2324d 100644 --- a/packages/webapp/src/routes/+layout.ts +++ b/packages/webapp/src/routes/+layout.ts @@ -1,6 +1,6 @@ import type { AppStoresInterface } from '@rainlanguage/ui-components'; -import { parseSettings } from '@rainlanguage/orderbook'; -import type { Config, OrderbookCfg } from '@rainlanguage/orderbook'; +import { parseYaml } from '@rainlanguage/orderbook'; +import type { Config, OrderbookCfg, OrderbookConfig } from '@rainlanguage/orderbook'; import { writable, derived, get } from 'svelte/store'; import pickBy from 'lodash/pickBy'; @@ -14,7 +14,7 @@ export const load = async ({ fetch }) => { ); const settingsYaml = await response.text(); - const settings = writable(parseSettings([settingsYaml])); + const settings = writable(parseYaml([settingsYaml]).orderbook); const activeNetworkRef = writable(''); const activeOrderbookRef = writable(''); const activeOrderbook = derived( @@ -142,42 +142,44 @@ subgraphs: key: 'subgraph3', url: 'https://subgraph3.url' }; - const mockSettingsJson = { - accounts: { - account1: { - name: 'Test Account 1' + const mockConfig = { + orderbook: { + accounts: { + account1: { + name: 'Test Account 1' + }, + account2: { + name: 'Test Account 2' + } }, - account2: { - name: 'Test Account 2' - } - }, - networks: { - network1, - network2 - }, - subgraphs: { - subgraph1, - subgraph2, - subgraph3 - }, - orderbooks: { - orderbook1: { - key: 'orderbook1', - address: '0x1234567890123456789012345678901234567890', - network: network1, - subgraph: subgraph1 + networks: { + network1, + network2 }, - orderbook2: { - key: 'orderbook2', - address: '0x1234567890123456789012345678901234567890', - network: network2, - subgraph: subgraph2 + subgraphs: { + subgraph1, + subgraph2, + subgraph3 }, - orderbook3: { - key: 'orderbook3', - address: '0x1234567890123456789012345678901234567890', - network: network1, - subgraph: subgraph3 + orderbooks: { + orderbook1: { + key: 'orderbook1', + address: '0x1234567890123456789012345678901234567890', + network: network1, + subgraph: subgraph1 + }, + orderbook2: { + key: 'orderbook2', + address: '0x1234567890123456789012345678901234567890', + network: network2, + subgraph: subgraph2 + }, + orderbook3: { + key: 'orderbook3', + address: '0x1234567890123456789012345678901234567890', + network: network1, + subgraph: subgraph3 + } } } }; @@ -188,7 +190,7 @@ subgraphs: }); it('should load settings and initialize stores correctly', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -217,7 +219,7 @@ subgraphs: expect(stores).toHaveProperty('subgraph'); expect(stores).toHaveProperty('activeNetworkOrderbooks'); - expect(get(stores.settings)).toEqual(mockSettingsJson); + expect(get(stores.settings)).toEqual(mockConfig.orderbook); expect(get(stores.activeNetworkRef)).toEqual(''); expect(get(stores.activeOrderbookRef)).toEqual(''); expect(get(stores.activeAccountsItems)).toEqual({}); @@ -226,7 +228,7 @@ subgraphs: }); it('should handle derived store: activeOrderbook', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -239,11 +241,11 @@ subgraphs: stores.activeOrderbookRef.set('orderbook1'); - expect(get(stores.activeOrderbook)).toEqual(mockSettingsJson.orderbooks.orderbook1); + expect(get(stores.activeOrderbook)).toEqual(mockConfig.orderbook.orderbooks.orderbook1); }); it('should handle derived store: activeNetworkOrderbooks', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -263,7 +265,7 @@ subgraphs: }); it('should handle derived store: subgraphUrl', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -280,7 +282,7 @@ subgraphs: }); it('should handle derived store: activeAccounts with empty activeAccountsItems', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -293,7 +295,7 @@ subgraphs: }); it('should handle derived store: activeAccounts with filled activeAccountsItems', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -317,7 +319,7 @@ subgraphs: }); it('should handle empty or malformed settings JSON', async () => { - vi.mocked(parseSettings).mockReturnValue({} as unknown as Config); + vi.mocked(parseYaml).mockReturnValue({ orderbook: {} } as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve({}) }); @@ -336,7 +338,7 @@ subgraphs: }); it('should handle chain reaction of store updates when changing network and orderbook', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -361,12 +363,12 @@ subgraphs: stores.activeOrderbookRef.set('orderbook1'); - expect(get(stores.activeOrderbook)).toEqual(mockSettingsJson.orderbooks.orderbook1); + expect(get(stores.activeOrderbook)).toEqual(mockConfig.orderbook.orderbooks.orderbook1); expect(get(stores.subgraph)).toEqual({ key: 'subgraph1', url: 'https://subgraph1.url' }); stores.activeNetworkRef.set('network2'); - expect(get(stores.activeOrderbook)).toEqual(mockSettingsJson.orderbooks.orderbook1); + expect(get(stores.activeOrderbook)).toEqual(mockConfig.orderbook.orderbooks.orderbook1); const newNetworkOrderbooks = get(stores.activeNetworkOrderbooks); expect(Object.keys(newNetworkOrderbooks).length).toBe(1); @@ -375,7 +377,7 @@ subgraphs: }); it('should handle multiple interrelated store updates correctly', async () => { - vi.mocked(parseSettings).mockReturnValue(mockSettingsJson as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) }); @@ -389,7 +391,7 @@ subgraphs: stores.activeAccountsItems.set({ account1: 'Account 1' }); expect(get(stores.activeNetworkOrderbooks)).toHaveProperty('orderbook1'); - expect(get(stores.activeOrderbook)).toEqual(mockSettingsJson.orderbooks.orderbook1); + expect(get(stores.activeOrderbook)).toEqual(mockConfig.orderbook.orderbooks.orderbook1); expect(get(stores.subgraph)).toEqual({ key: 'subgraph1', url: 'https://subgraph1.url' }); expect(get(stores.activeAccounts)).toHaveProperty('account1'); @@ -401,7 +403,7 @@ subgraphs: expect(get(stores.activeNetworkOrderbooks)).toHaveProperty('orderbook2'); expect(get(stores.activeNetworkOrderbooks)).not.toHaveProperty('orderbook1'); - expect(get(stores.activeOrderbook)).toEqual(mockSettingsJson.orderbooks.orderbook2); + expect(get(stores.activeOrderbook)).toEqual(mockConfig.orderbook.orderbooks.orderbook2); expect(get(stores.subgraph)).toEqual({ key: 'subgraph2', url: 'https://subgraph2.url' }); const finalAccounts = get(stores.activeAccounts); @@ -432,7 +434,7 @@ orderbooks: network: network1 `; - vi.mocked(parseSettings).mockReturnValue(partialSettings as unknown as Config); + vi.mocked(parseYaml).mockReturnValue(partialSettings as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(partialSettings) }); diff --git a/packages/webapp/test-setup.ts b/packages/webapp/test-setup.ts index 56a8ada6b..31e5ffcf2 100644 --- a/packages/webapp/test-setup.ts +++ b/packages/webapp/test-setup.ts @@ -15,6 +15,6 @@ vi.mock('@rainlanguage/orderbook', () => { DotrainOrderGui.prototype.chooseDeployment = vi.fn(); return { DotrainOrderGui, - parseSettings: vi.fn() + parseYaml: vi.fn() }; }); diff --git a/tauri-app/src-tauri/src/commands/config.rs b/tauri-app/src-tauri/src/commands/config.rs index 7b5db9fec..82c6d43e6 100644 --- a/tauri-app/src-tauri/src/commands/config.rs +++ b/tauri-app/src-tauri/src/commands/config.rs @@ -4,7 +4,7 @@ use rain_orderbook_app_settings::config::Config; #[tauri::command] pub fn parse_configstring(text: String, validate: bool) -> CommandResult { - Ok(Config::try_from_settings(vec![text], validate)?) + Ok(Config::try_from_yaml(vec![text], validate)?) } #[tauri::command] @@ -16,7 +16,7 @@ pub fn merge_configstrings( let frontmatter = RainDocument::get_front_matter(dotrain.as_str()) .unwrap_or("") .to_string(); - Ok(Config::try_from_settings( + Ok(Config::try_from_yaml( vec![frontmatter, config_text], validate, )?) diff --git a/tauri-app/src/lib/components/ModalExecute.svelte b/tauri-app/src/lib/components/ModalExecute.svelte index 9a3403546..9316dfaad 100644 --- a/tauri-app/src/lib/components/ModalExecute.svelte +++ b/tauri-app/src/lib/components/ModalExecute.svelte @@ -32,7 +32,7 @@ } const getNetworkName = (chainId: number) => { - const existingNetwork = Object.entries($settings?.networks || {}).find( + const existingNetwork = Object.entries($settings.orderbook.networks || {}).find( (entry) => entry[1].chainId === chainId, ); diff --git a/tauri-app/src/lib/components/ModalExecute.test.ts b/tauri-app/src/lib/components/ModalExecute.test.ts index 42e1cd317..7009ea0e4 100644 --- a/tauri-app/src/lib/components/ModalExecute.test.ts +++ b/tauri-app/src/lib/components/ModalExecute.test.ts @@ -32,7 +32,9 @@ describe('ModalExecute', () => { vi.clearAllMocks(); // Reset settings store before each test settings.set({ - networks: {}, + orderbook: { + networks: {}, + }, } as unknown as Config); }); @@ -62,11 +64,13 @@ describe('ModalExecute', () => { it('should show current connected network name when network is in settings', () => { settings.set({ - networks: { - mainnet: { - key: 'mainnet', - chainId: 1, - rpc: 'https://mainnet.com', + orderbook: { + networks: { + mainnet: { + key: 'mainnet', + chainId: 1, + rpc: 'https://mainnet.com', + }, }, }, } as unknown as Config); diff --git a/tauri-app/src/lib/services/pickConfig.ts b/tauri-app/src/lib/services/pickConfig.ts index e4093df92..15500864b 100644 --- a/tauri-app/src/lib/services/pickConfig.ts +++ b/tauri-app/src/lib/services/pickConfig.ts @@ -5,11 +5,14 @@ export function pickDeployments( mergedConfig: Config | undefined, activeNetworkRef: string | undefined, ) { - return !isNil(mergedConfig) && !isNil(mergedConfig?.deployments) && !isNil(mergedConfig?.orders) + return !isNil(mergedConfig) && + !isNil(mergedConfig?.dotrainOrder.deployments) && + !isNil(mergedConfig?.dotrainOrder.orders) ? pickBy( - mergedConfig.deployments, + mergedConfig.dotrainOrder.deployments, (d) => - mergedConfig?.scenarios?.[d.scenario.key]?.deployer?.network?.key === activeNetworkRef, + mergedConfig?.dotrainOrder.scenarios?.[d.scenario.key]?.deployer?.network?.key === + activeNetworkRef, ) : {}; } @@ -19,6 +22,9 @@ export function pickScenarios( activeNetworkRef: string | undefined, ) { return !isNil(mergedConfig) - ? pickBy(mergedConfig.scenarios, (d) => d?.deployer?.network?.key === activeNetworkRef) + ? pickBy( + mergedConfig.dotrainOrder.scenarios, + (d) => d?.deployer?.network?.key === activeNetworkRef, + ) : {}; } diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts index 1b9863461..db9346d01 100644 --- a/tauri-app/src/lib/stores/settings.test.ts +++ b/tauri-app/src/lib/stores/settings.test.ts @@ -5,49 +5,51 @@ import type { Config, NetworkCfg, SubgraphCfg } from '@rainlanguage/orderbook'; // Define the mock directly in the tests const mockConfig: Config = { - networks: { - mainnet: { - key: 'mainnet', - rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', - chainId: 1, - label: 'Ethereum Mainnet', - currency: 'ETH', - }, - }, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'https://api.thegraph.com/subgraphs/name/mainnet', - }, - }, - orderbooks: { - orderbook1: { - key: 'orderbook1', - address: '0xOrderbookAddress1', - network: { + orderbook: { + networks: { + mainnet: { key: 'mainnet', - } as unknown as NetworkCfg, - subgraph: { - key: 'uniswap', - } as unknown as SubgraphCfg, - label: 'Orderbook 1', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH', + }, }, - }, - deployers: { - deployer1: { - key: 'deployer1', - address: '0xDeployerAddress1', - network: { + subgraphs: { + mainnet: { key: 'mainnet', - } as unknown as NetworkCfg, + url: 'https://api.thegraph.com/subgraphs/name/mainnet', + }, + }, + orderbooks: { + orderbook1: { + key: 'orderbook1', + address: '0xOrderbookAddress1', + network: { + key: 'mainnet', + } as unknown as NetworkCfg, + subgraph: { + key: 'uniswap', + } as unknown as SubgraphCfg, + label: 'Orderbook 1', + }, + }, + deployers: { + deployer1: { + key: 'deployer1', + address: '0xDeployerAddress1', + network: { + key: 'mainnet', + } as unknown as NetworkCfg, + }, + }, + metaboards: { + metaboard1: 'https://example.com/metaboard1', + }, + accounts: { + name_one: 'address_one', + name_two: 'address_two', }, - }, - metaboards: { - metaboard1: 'https://example.com/metaboard1', - }, - accounts: { - name_one: 'address_one', - name_two: 'address_two', }, } as unknown as Config; @@ -89,13 +91,16 @@ describe('Settings active accounts items', () => { test('should remove account if that account is removed', () => { // Test removing an account const newSettings = { - ...mockConfig, - accounts: { - name_one: { - key: 'name_one', - address: 'address_one', + orderbook: { + ...mockConfig.orderbook, + accounts: { + name_one: { + key: 'name_one', + address: 'address_one', + }, }, }, + dotrainOrder: mockConfig.dotrainOrder, }; // Update settings - this should trigger the subscription @@ -109,17 +114,20 @@ describe('Settings active accounts items', () => { test('should remove account if the value is different', () => { const newSettings = { - ...mockConfig, - accounts: { - name_one: { - key: 'name_one', - address: 'address_one', - }, - name_two: { - key: 'name_two', - address: 'new_value', + orderbook: { + ...mockConfig.orderbook, + accounts: { + name_one: { + key: 'name_one', + address: 'address_one', + }, + name_two: { + key: 'name_two', + address: 'new_value', + }, }, }, + dotrainOrder: mockConfig.dotrainOrder, }; settings.set(newSettings); @@ -131,13 +139,16 @@ describe('Settings active accounts items', () => { test('should update active subgraphs when subgraph value changes', () => { const newSettings = { - ...mockConfig, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'new value', + orderbook: { + ...mockConfig.orderbook, + subgraphs: { + mainnet: { + key: 'mainnet', + url: 'new value', + }, }, }, + dotrainOrder: mockConfig.dotrainOrder, }; settings.set(newSettings as unknown as Config); @@ -152,13 +163,16 @@ describe('Settings active accounts items', () => { test('should update active subgraphs when subgraph removed', () => { const newSettings = { - ...mockConfig, - subgraphs: { - testnet: { - key: 'testnet', - url: 'testnet', + orderbook: { + ...mockConfig.orderbook, + subgraphs: { + testnet: { + key: 'testnet', + url: 'testnet', + }, }, }, + dotrainOrder: mockConfig.dotrainOrder, }; settings.set(newSettings as unknown as Config); @@ -168,8 +182,11 @@ describe('Settings active accounts items', () => { test('should reset active subgraphs when subgraphs are undefined', () => { const newSettings = { - ...mockConfig, - subgraphs: undefined, + orderbook: { + ...mockConfig.orderbook, + subgraphs: undefined, + }, + dotrainOrder: mockConfig.dotrainOrder, }; settings.set(newSettings as unknown as Config); diff --git a/tauri-app/src/lib/stores/settings.ts b/tauri-app/src/lib/stores/settings.ts index 7b8edaf2b..a8c9cea58 100644 --- a/tauri-app/src/lib/stores/settings.ts +++ b/tauri-app/src/lib/stores/settings.ts @@ -6,22 +6,31 @@ import { import find from 'lodash/find'; import * as chains from 'viem/chains'; import { textFileStore } from '$lib/storesGeneric/textFileStore'; -import { type Config, type OrderbookCfg, type SubgraphCfg } from '@rainlanguage/orderbook'; +import { + parseYaml, + type Config, + type OrderbookCfg, + type SubgraphCfg, +} from '@rainlanguage/orderbook'; import { getBlockNumberFromRpc } from '$lib/services/chain'; import { pickBy } from 'lodash'; export const EMPTY_SETTINGS: Config = { - networks: {}, - subgraphs: {}, - metaboards: {}, - orderbooks: {}, - accounts: {}, - tokens: {}, - deployers: {}, - orders: {}, - scenarios: {}, - charts: {}, - deployments: {}, + orderbook: { + networks: {}, + subgraphs: {}, + metaboards: {}, + orderbooks: {}, + accounts: {}, + tokens: {}, + deployers: {}, + }, + dotrainOrder: { + orders: {}, + scenarios: {}, + charts: {}, + deployments: {}, + }, }; // general @@ -42,14 +51,14 @@ export const settings = cachedWritableStore( (value) => JSON.stringify(value), (str) => { try { - return JSON.parse(str) as Config; + return parseYaml([str]); } catch { return EMPTY_SETTINGS; } }, ); export const enableSentry = derived(settings, ($settings) => - $settings?.sentry !== undefined ? $settings.sentry : true, + $settings.orderbook.sentry !== undefined ? $settings.orderbook.sentry : true, ); // networks @@ -57,8 +66,8 @@ export const activeNetworkRef = cachedWritableStringOptional('settings.activeNet export const activeNetwork = asyncDerived( [settings, activeNetworkRef], async ([$settings, $activeNetworkRef]) => { - return $activeNetworkRef !== undefined && $settings?.networks !== undefined - ? $settings.networks[$activeNetworkRef] + return $activeNetworkRef !== undefined && $settings.orderbook.networks !== undefined + ? $settings.orderbook.networks[$activeNetworkRef] : undefined; }, ); @@ -79,9 +88,9 @@ export const activeOrderbookRef = cachedWritableStringOptional('settings.activeO export const activeNetworkOrderbooks = derived( [settings, activeNetworkRef], ([$settings, $activeNetworkRef]) => - $settings?.orderbooks + $settings.orderbook.orderbooks ? (pickBy( - $settings.orderbooks, + $settings.orderbook.orderbooks, (orderbook) => orderbook.network.key === $activeNetworkRef, ) as Record) : ({} as Record), @@ -89,13 +98,13 @@ export const activeNetworkOrderbooks = derived( export const activeOrderbook = derived( [settings, activeOrderbookRef], ([$settings, $activeOrderbookRef]) => - $settings?.orderbooks !== undefined && $activeOrderbookRef !== undefined - ? $settings.orderbooks[$activeOrderbookRef] + $settings.orderbook.orderbooks !== undefined && $activeOrderbookRef !== undefined + ? $settings.orderbook.orderbooks[$activeOrderbookRef] : undefined, ); export const subgraph = derived([settings, activeOrderbook], ([$settings, $activeOrderbook]) => - $settings?.subgraphs !== undefined && $activeOrderbook?.subgraph !== undefined - ? $settings.subgraphs[$activeOrderbook.subgraph.key] + $settings.orderbook.subgraphs !== undefined && $activeOrderbook?.subgraph !== undefined + ? $settings.orderbook.subgraphs[$activeOrderbook.subgraph.key] : undefined, ); export const orderbookAddress = derived( @@ -110,7 +119,7 @@ export const hasRequiredSettings = derived( ); // accounts -export const accounts = derived(settings, ($settings) => $settings?.accounts ?? {}); +export const accounts = derived(settings, ($settings) => $settings.orderbook.accounts ?? {}); export const activeAccountsItems = cachedWritableStore>( 'settings.activeAccountsItems', {}, @@ -135,7 +144,7 @@ export const activeAccounts = derived( // subgraphs export const subgraphs = derived(settings, ($settings) => - $settings?.subgraphs !== undefined ? Object.entries($settings.subgraphs) : [], + $settings.orderbook.subgraphs !== undefined ? Object.entries($settings.orderbook.subgraphs) : [], ); export const activeSubgraphs = cachedWritableStore>( 'settings.activeSubgraphs', @@ -157,32 +166,32 @@ settings.subscribe(async () => { const $activeOrderbookRef = get(activeOrderbookRef); if ( - $settings?.networks === undefined || + $settings.orderbook.networks === undefined || $activeNetworkRef === undefined || - ($settings?.networks !== undefined && + ($settings.orderbook.networks !== undefined && $activeNetworkRef !== undefined && - !Object.keys($settings.networks).includes($activeNetworkRef)) + !Object.keys($settings.orderbook.networks).includes($activeNetworkRef)) ) { resetActiveNetworkRef(); } if ( - !$settings?.orderbooks === undefined || + !$settings.orderbook.orderbooks === undefined || $activeOrderbookRef === undefined || - ($settings?.orderbooks !== undefined && + ($settings.orderbook.orderbooks !== undefined && $activeOrderbookRef !== undefined && - !Object.keys($settings.orderbooks).includes($activeOrderbookRef)) + !Object.keys($settings.orderbook.orderbooks).includes($activeOrderbookRef)) ) { resetActiveOrderbookRef(); } // Reset active account items if accounts have changed - if ($settings?.accounts === undefined) { + if ($settings.orderbook.accounts === undefined) { activeAccountsItems.set({}); } else { const currentActiveAccounts = get(activeAccountsItems); const updatedActiveAccounts = Object.fromEntries( - Object.entries($settings.accounts ?? {}) + Object.entries($settings.orderbook.accounts ?? {}) .filter(([key, value]) => { if (key in currentActiveAccounts) { return currentActiveAccounts[key] === value.address; @@ -195,12 +204,12 @@ settings.subscribe(async () => { } // Reset active subgraphs if subgraphs have changed - if ($settings?.subgraphs === undefined) { + if ($settings.orderbook.subgraphs === undefined) { activeSubgraphs.set({}); } else { const currentActiveSubgraphs = get(activeSubgraphs); const updatedActiveSubgraphs = Object.fromEntries( - Object.entries($settings.subgraphs).filter(([key, value]) => { + Object.entries($settings.orderbook.subgraphs).filter(([key, value]) => { if (key in currentActiveSubgraphs) { return currentActiveSubgraphs[key].key === value.key; } @@ -244,7 +253,7 @@ export function resetActiveOrderbookRef() { // reset active orderbook to first available, otherwise undefined export async function resetActiveNetworkRef() { - const $networks = get(settings)?.networks; + const $networks = get(settings)?.orderbook.networks; if ($networks !== undefined && Object.keys($networks).length > 0) { activeNetworkRef.set(Object.keys($networks)[0]); diff --git a/tauri-app/src/lib/stores/walletconnect.ts b/tauri-app/src/lib/stores/walletconnect.ts index b79e634e3..1bcecd69c 100644 --- a/tauri-app/src/lib/stores/walletconnect.ts +++ b/tauri-app/src/lib/stores/walletconnect.ts @@ -71,8 +71,8 @@ export async function walletconnectConnect(priorityChainIds: number[]) { const $settings = get(settings); - if ($settings?.networks) { - for (const network of Object.values($settings.networks)) { + if ($settings?.orderbook.networks) { + for (const network of Object.values($settings.orderbook.networks)) { rpcMap[network.chainId] = network.rpc; chains.push(network.chainId); } diff --git a/tauri-app/src/tests/pickConfig.test.ts b/tauri-app/src/tests/pickConfig.test.ts index f34ddbd30..c39720f54 100644 --- a/tauri-app/src/tests/pickConfig.test.ts +++ b/tauri-app/src/tests/pickConfig.test.ts @@ -4,94 +4,45 @@ import { pickDeployments, pickScenarios } from '$lib/services/pickConfig'; import type { Config, DeploymentCfg, ScenarioCfg } from '@rainlanguage/orderbook'; export const config: Config = { - networks: { - network1: { - key: 'network1', - rpc: 'rpc-url', - chainId: 14, - }, - network2: { - key: 'network2', - rpc: 'rpc-url', - chainId: 137, - }, - }, - subgraphs: { - network1: { - key: 'some-key', - url: 'some-url', - }, - }, - metaboards: { - network1: 'some-url', - }, - orderbooks: { - network1: { - key: 'network1', - address: '0x123456', - network: { + orderbook: { + networks: { + network1: { key: 'network1', rpc: 'rpc-url', chainId: 14, }, - subgraph: { - key: 'some-key', - url: 'some-url', - }, - }, - }, - deployers: { - network1: { - key: 'network1', - address: '0xabcdef', - network: { - key: 'network1', + network2: { + key: 'network2', rpc: 'rpc-url', - chainId: 14, + chainId: 137, }, }, - }, - tokens: {}, - orders: { - buy: { - key: 'buy', - inputs: [], - outputs: [], - network: { - key: 'network1', - rpc: 'rpc-url', - chainId: 14, + subgraphs: { + network1: { + key: 'some-key', + url: 'some-url', }, }, - sell: { - key: 'sell', - inputs: [], - outputs: [], - network: { - key: 'network1', - rpc: 'rpc-url', - chainId: 14, - }, + metaboards: { + network1: 'some-url', }, - }, - scenarios: { - 'network1.sell': { - key: 'network1.sell', - bindings: {}, - deployer: { + orderbooks: { + network1: { key: 'network1', - address: '0xabcdef', + address: '0x123456', network: { key: 'network1', rpc: 'rpc-url', chainId: 14, }, + subgraph: { + key: 'some-key', + url: 'some-url', + }, }, }, - network1: { - key: 'network1', - bindings: {}, - deployer: { + deployers: { + network1: { key: 'network1', address: '0xabcdef', network: { @@ -101,12 +52,34 @@ export const config: Config = { }, }, }, - 'network1.buy': { - key: 'network1.buy', - bindings: {}, - deployer: { - key: 'network1', - address: '0xabcdef', + tokens: {}, + accounts: { + name_one: { + key: 'name_one', + address: 'address_one', + }, + name_two: { + key: 'name_two', + address: 'address_two', + }, + }, + }, + dotrainOrder: { + orders: { + buy: { + key: 'buy', + inputs: [], + outputs: [], + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, + }, + sell: { + key: 'sell', + inputs: [], + outputs: [], network: { key: 'network1', rpc: 'rpc-url', @@ -114,12 +87,8 @@ export const config: Config = { }, }, }, - }, - charts: {}, - deployments: { - sell: { - key: 'sell', - scenario: { + scenarios: { + 'network1.sell': { key: 'network1.sell', bindings: {}, deployer: { @@ -132,20 +101,20 @@ export const config: Config = { }, }, }, - order: { - key: 'sell', - inputs: [], - outputs: [], - network: { + network1: { + key: 'network1', + bindings: {}, + deployer: { key: 'network1', - rpc: 'rpc-url', - chainId: 14, + address: '0xabcdef', + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, }, }, - }, - buy: { - key: 'buy', - scenario: { + 'network1.buy': { key: 'network1.buy', bindings: {}, deployer: { @@ -158,28 +127,63 @@ export const config: Config = { }, }, }, - order: { + }, + charts: {}, + deployments: { + sell: { + key: 'sell', + scenario: { + key: 'network1.sell', + bindings: {}, + deployer: { + key: 'network1', + address: '0xabcdef', + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, + }, + }, + order: { + key: 'sell', + inputs: [], + outputs: [], + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, + }, + }, + buy: { key: 'buy', - inputs: [], - outputs: [], - network: { - key: 'network1', - rpc: 'rpc-url', - chainId: 14, + scenario: { + key: 'network1.buy', + bindings: {}, + deployer: { + key: 'network1', + address: '0xabcdef', + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, + }, + }, + order: { + key: 'buy', + inputs: [], + outputs: [], + network: { + key: 'network1', + rpc: 'rpc-url', + chainId: 14, + }, }, }, }, }, - accounts: { - name_one: { - key: 'name_one', - address: 'address_one', - }, - name_two: { - key: 'name_two', - address: 'address_two', - }, - }, }; test('pick deployments', () => { From 65d10ed4d8007f580dd301a95c96540876a70c2c Mon Sep 17 00:00:00 2001 From: findolor Date: Mon, 28 Apr 2025 10:39:02 +0300 Subject: [PATCH 39/51] update based on ai comments --- packages/ui-components/src/lib/types/appStores.ts | 10 ++++++++-- packages/webapp/src/routes/+layout.ts | 2 +- tauri-app/src/lib/services/pickConfig.ts | 6 ++---- tauri-app/src/lib/stores/settings.ts | 2 +- tauri-app/src/tests/pickConfig.test.ts | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/ui-components/src/lib/types/appStores.ts b/packages/ui-components/src/lib/types/appStores.ts index 2f27db288..9b017b4c0 100644 --- a/packages/ui-components/src/lib/types/appStores.ts +++ b/packages/ui-components/src/lib/types/appStores.ts @@ -1,7 +1,13 @@ import type { Readable, Writable } from 'svelte/store'; -import type { AccountCfg, Config, OrderbookCfg, SubgraphCfg } from '@rainlanguage/orderbook'; +import type { + AccountCfg, + OrderbookCfg, + OrderbookConfig, + SubgraphCfg +} from '@rainlanguage/orderbook'; + export interface AppStoresInterface { - settings: Writable; + settings: Writable; activeSubgraphs: Writable>; accounts: Readable>; activeAccountsItems: Writable>; diff --git a/packages/webapp/src/routes/+layout.ts b/packages/webapp/src/routes/+layout.ts index f2ec2324d..38442a6a6 100644 --- a/packages/webapp/src/routes/+layout.ts +++ b/packages/webapp/src/routes/+layout.ts @@ -264,7 +264,7 @@ subgraphs: expect(networkOrderbooks).not.toHaveProperty('orderbook2'); }); - it('should handle derived store: subgraphUrl', async () => { + it('should handle derived store: subgraph', async () => { vi.mocked(parseYaml).mockReturnValue(mockConfig as unknown as Config); mockFetch.mockResolvedValueOnce({ text: () => Promise.resolve(mockSettingsYaml) diff --git a/tauri-app/src/lib/services/pickConfig.ts b/tauri-app/src/lib/services/pickConfig.ts index 15500864b..ee1a6bc85 100644 --- a/tauri-app/src/lib/services/pickConfig.ts +++ b/tauri-app/src/lib/services/pickConfig.ts @@ -5,13 +5,11 @@ export function pickDeployments( mergedConfig: Config | undefined, activeNetworkRef: string | undefined, ) { - return !isNil(mergedConfig) && - !isNil(mergedConfig?.dotrainOrder.deployments) && - !isNil(mergedConfig?.dotrainOrder.orders) + return mergedConfig?.dotrainOrder?.deployments && mergedConfig?.dotrainOrder?.orders ? pickBy( mergedConfig.dotrainOrder.deployments, (d) => - mergedConfig?.dotrainOrder.scenarios?.[d.scenario.key]?.deployer?.network?.key === + mergedConfig.dotrainOrder.scenarios?.[d.scenario.key]?.deployer?.network?.key === activeNetworkRef, ) : {}; diff --git a/tauri-app/src/lib/stores/settings.ts b/tauri-app/src/lib/stores/settings.ts index a8c9cea58..c887feac3 100644 --- a/tauri-app/src/lib/stores/settings.ts +++ b/tauri-app/src/lib/stores/settings.ts @@ -176,7 +176,7 @@ settings.subscribe(async () => { } if ( - !$settings.orderbook.orderbooks === undefined || + $settings.orderbook.orderbooks === undefined || $activeOrderbookRef === undefined || ($settings.orderbook.orderbooks !== undefined && $activeOrderbookRef !== undefined && diff --git a/tauri-app/src/tests/pickConfig.test.ts b/tauri-app/src/tests/pickConfig.test.ts index c39720f54..db54a0ac2 100644 --- a/tauri-app/src/tests/pickConfig.test.ts +++ b/tauri-app/src/tests/pickConfig.test.ts @@ -3,7 +3,7 @@ import type { Dictionary } from 'lodash'; import { pickDeployments, pickScenarios } from '$lib/services/pickConfig'; import type { Config, DeploymentCfg, ScenarioCfg } from '@rainlanguage/orderbook'; -export const config: Config = { +const config: Config = { orderbook: { networks: { network1: { From 2fb5528669c4e5dd2acad224244d850fe9fe0821 Mon Sep 17 00:00:00 2001 From: findolor Date: Mon, 28 Apr 2025 10:49:25 +0300 Subject: [PATCH 40/51] linter fix --- .../src/__tests__/DeploymentSteps.test.ts | 8 +- .../__tests__/DropdownActiveSubgraphs.test.ts | 30 ++--- .../src/lib/__mocks__/settings.ts | 108 +++++++++--------- .../ListViewOrderbookFilters.svelte | 2 +- .../deployment/DeploymentSteps.svelte | 2 +- .../dropdown/DropdownActiveSubgraphs.svelte | 4 +- .../components/tables/OrdersListTable.svelte | 4 +- .../components/tables/VaultsListTable.svelte | 4 +- packages/ui-components/src/lib/index.ts | 2 +- .../ui-components/src/lib/types/appStores.ts | 9 +- .../orders/[network]-[orderHash]/+page.svelte | 12 +- tauri-app/src/routes/orders/add/+page.svelte | 7 +- 12 files changed, 99 insertions(+), 93 deletions(-) diff --git a/packages/ui-components/src/__tests__/DeploymentSteps.test.ts b/packages/ui-components/src/__tests__/DeploymentSteps.test.ts index ff589381b..bd70c77cf 100644 --- a/packages/ui-components/src/__tests__/DeploymentSteps.test.ts +++ b/packages/ui-components/src/__tests__/DeploymentSteps.test.ts @@ -10,7 +10,7 @@ import userEvent from '@testing-library/user-event'; import { useGui } from '$lib/hooks/useGui'; import { useAccount } from '$lib/providers/wallet/useAccount'; import { handleDeployment } from '../lib/components/deployment/handleDeployment'; -import { mockConfigSource } from '../lib/__mocks__/settings'; +import { mockConfig } from '../lib/__mocks__/settings'; import type { DeploymentArgs } from '$lib/types/transaction'; const { mockConnectedStore } = await vi.hoisted(() => import('../lib/__mocks__/stores')); @@ -90,7 +90,7 @@ const defaultProps: DeploymentStepsProps = { wagmiConnected: mockConnectedStore, appKitModal: writable({} as AppKit), onDeploy: mockOnDeploy, - settings: writable(mockConfigSource), + settings: writable(mockConfig), registryUrl: 'https://registry.reown.xyz' } as DeploymentStepsProps; @@ -163,7 +163,7 @@ describe('DeploymentSteps', () => { expect(handleDeployment).toHaveBeenCalledWith( mockGui, '0x123', - mockConfigSource.subgraphs?.mainnet.url + mockConfig.orderbook.subgraphs.mainnet.url ); }); }); @@ -466,7 +466,7 @@ describe('DeploymentSteps', () => { expect(guiArg).toBe(mockGui); expect(accountArg).toBe('0xTestAccount'); - expect(subgraphUrlArg).toBe(mockConfigSource.subgraphs?.flare.url); + expect(subgraphUrlArg).toBe(mockConfig.orderbook.subgraphs.flare.url); }); }); }); diff --git a/packages/ui-components/src/__tests__/DropdownActiveSubgraphs.test.ts b/packages/ui-components/src/__tests__/DropdownActiveSubgraphs.test.ts index 86c781569..8f8b2f978 100644 --- a/packages/ui-components/src/__tests__/DropdownActiveSubgraphs.test.ts +++ b/packages/ui-components/src/__tests__/DropdownActiveSubgraphs.test.ts @@ -2,24 +2,26 @@ import { render, fireEvent, screen, waitFor } from '@testing-library/svelte'; import { get, writable, type Writable } from 'svelte/store'; import { beforeEach, expect, test, describe } from 'vitest'; import DropdownActiveSubgraphs from '../lib/components/dropdown/DropdownActiveSubgraphs.svelte'; -import { mockConfigSource } from '../lib/__mocks__/settings'; +import { mockConfig } from '../lib/__mocks__/settings'; import type { Config, SubgraphCfg } from '@rainlanguage/orderbook'; describe('DropdownActiveSubgraphs', () => { const mockSettings = { - ...mockConfigSource, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'mainnet' - }, - testnet: { - key: 'testnet', - url: 'testnet' - }, - local: { - key: 'local', - url: 'local' + orderbook: { + ...mockConfig.orderbook, + subgraphs: { + mainnet: { + key: 'mainnet', + url: 'mainnet' + }, + testnet: { + key: 'testnet', + url: 'testnet' + }, + local: { + key: 'local', + url: 'local' + } } } } as unknown as Config; diff --git a/packages/ui-components/src/lib/__mocks__/settings.ts b/packages/ui-components/src/lib/__mocks__/settings.ts index cbde997c4..85bbd8afa 100644 --- a/packages/ui-components/src/lib/__mocks__/settings.ts +++ b/packages/ui-components/src/lib/__mocks__/settings.ts @@ -1,71 +1,73 @@ import type { Config } from '@rainlanguage/orderbook'; import { writable } from 'svelte/store'; -export const mockConfigSource: Config = { - networks: { - mainnet: { - key: 'mainnet', - rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', - chainId: 1, - label: 'Ethereum Mainnet', - currency: 'ETH' - } - }, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'https://api.thegraph.com/subgraphs/name/mainnet' - }, - flare: { - key: 'flare', - url: 'https://api.thegraph.com/subgraphs/name/flare' - }, - testnet: { - key: 'testnet', - url: 'https://api.thegraph.com/subgraphs/name/testnet' - } - }, - orderbooks: { - orderbook1: { - key: 'orderbook1', - address: '0xOrderbookAddress1', - network: { +export const mockConfig: Config = { + orderbook: { + networks: { + mainnet: { key: 'mainnet', rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', chainId: 1, label: 'Ethereum Mainnet', currency: 'ETH' + } + }, + subgraphs: { + mainnet: { + key: 'mainnet', + url: 'https://api.thegraph.com/subgraphs/name/mainnet' }, - subgraph: { - key: 'uniswap', - url: 'https://api.thegraph.com/subgraphs/name/uniswap' + flare: { + key: 'flare', + url: 'https://api.thegraph.com/subgraphs/name/flare' }, - label: 'Orderbook 1' - } - }, - deployers: { - deployer1: { - key: 'deployer1', - address: '0xDeployerAddress1', - network: { - key: 'mainnet', - rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', - chainId: 1, - label: 'Ethereum Mainnet', - currency: 'ETH' + testnet: { + key: 'testnet', + url: 'https://api.thegraph.com/subgraphs/name/testnet' + } + }, + orderbooks: { + orderbook1: { + key: 'orderbook1', + address: '0xOrderbookAddress1', + network: { + key: 'mainnet', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH' + }, + subgraph: { + key: 'uniswap', + url: 'https://api.thegraph.com/subgraphs/name/uniswap' + }, + label: 'Orderbook 1' } + }, + deployers: { + deployer1: { + key: 'deployer1', + address: '0xDeployerAddress1', + network: { + key: 'mainnet', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH' + } + } + }, + metaboards: { + metaboard1: 'https://example.com/metaboard1' + }, + accounts: { + name_one: 'address_one', + name_two: 'address_two' } - }, - metaboards: { - metaboard1: 'https://example.com/metaboard1' - }, - accounts: { - name_one: 'address_one', - name_two: 'address_two' } } as unknown as Config; -const mockSettingsStoreWritable = writable(mockConfigSource); +const mockSettingsStoreWritable = writable(mockConfig); export const mockSettingsStore = { subscribe: mockSettingsStoreWritable.subscribe, diff --git a/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte b/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte index 88022d0cd..f31bc01f3 100644 --- a/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte +++ b/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte @@ -30,7 +30,7 @@ class="grid w-full items-center gap-4 md:flex md:justify-end lg:min-w-[600px]" style="grid-template-columns: repeat(2, minmax(0, 1fr));" > - {#if isEmpty($settings?.networks)} + {#if isEmpty($settings?.orderbook?.networks)} No networks added to settings diff --git a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte index 6b88d43c5..2913bd4a3 100644 --- a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte +++ b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte @@ -71,7 +71,7 @@ if (error) { DeploymentStepsError.catch(error, DeploymentStepsErrorCode.NO_NETWORK_KEY); } else if (value) { - subgraphUrl = $settings?.subgraphs?.[value].url; + subgraphUrl = $settings.orderbook.subgraphs[value].url; } await areAllTokensSelected(); }); diff --git a/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte b/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte index 714127da1..fd21ffb15 100644 --- a/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte +++ b/packages/ui-components/src/lib/components/dropdown/DropdownActiveSubgraphs.svelte @@ -6,7 +6,7 @@ export let settings: Config; export let activeSubgraphs: Writable>; - $: dropdownOptions = Object.keys(settings?.subgraphs ?? {}).reduce( + $: dropdownOptions = Object.keys(settings.orderbook.subgraphs ?? {}).reduce( (acc, key) => ({ ...acc, [key]: key @@ -18,7 +18,7 @@ let items = Object.keys(event.detail); activeSubgraphs.set( Object.values(items).reduce( - (acc, key) => ({ ...acc, [key]: (settings?.subgraphs ?? {})[key] }), + (acc, key) => ({ ...acc, [key]: (settings.orderbook.subgraphs ?? {})[key] }), {} as Record ) ); diff --git a/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte b/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte index a8ad6e8fe..e697d4fce 100644 --- a/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte +++ b/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte @@ -43,7 +43,9 @@ const { matchesAccount, account } = useAccount(); $: multiSubgraphArgs = Object.entries( - Object.keys($activeSubgraphs ?? {}).length ? $activeSubgraphs : ($settings?.subgraphs ?? {}) + Object.keys($activeSubgraphs ?? {}).length + ? $activeSubgraphs + : ($settings?.orderbook?.subgraphs ?? {}) ).map(([name, value]) => ({ name, url: value.url diff --git a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte index bdcd8f104..6e8846cb2 100644 --- a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte +++ b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte @@ -52,7 +52,9 @@ const { account, matchesAccount } = useAccount(); $: multiSubgraphArgs = Object.entries( - Object.keys($activeSubgraphs ?? {}).length ? $activeSubgraphs : ($settings?.subgraphs ?? {}) + Object.keys($activeSubgraphs ?? {}).length + ? $activeSubgraphs + : ($settings?.orderbook?.subgraphs ?? {}) ).map(([name, value]) => ({ name, url: value.url diff --git a/packages/ui-components/src/lib/index.ts b/packages/ui-components/src/lib/index.ts index 63c0441a4..4f8ec26ae 100644 --- a/packages/ui-components/src/lib/index.ts +++ b/packages/ui-components/src/lib/index.ts @@ -131,6 +131,6 @@ export { useAccount } from './providers/wallet/useAccount'; // Mocks export { mockPageStore } from './__mocks__/stores'; -export { mockConfigSource } from './__mocks__/settings'; +export { mockConfig } from './__mocks__/settings'; export { mockSettingsStore } from './__mocks__/settings'; export { mockTransactionStore } from './__mocks__/mockTransactionStore'; diff --git a/packages/ui-components/src/lib/types/appStores.ts b/packages/ui-components/src/lib/types/appStores.ts index 9b017b4c0..534ff816a 100644 --- a/packages/ui-components/src/lib/types/appStores.ts +++ b/packages/ui-components/src/lib/types/appStores.ts @@ -1,13 +1,8 @@ import type { Readable, Writable } from 'svelte/store'; -import type { - AccountCfg, - OrderbookCfg, - OrderbookConfig, - SubgraphCfg -} from '@rainlanguage/orderbook'; +import type { AccountCfg, Config, OrderbookCfg, SubgraphCfg } from '@rainlanguage/orderbook'; export interface AppStoresInterface { - settings: Writable; + settings: Writable; activeSubgraphs: Writable>; accounts: Readable>; activeAccountsItems: Writable>; diff --git a/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte b/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte index 4f8d14cb4..198f04976 100644 --- a/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte +++ b/tauri-app/src/routes/orders/[network]-[orderHash]/+page.svelte @@ -24,16 +24,16 @@ let rpcUrl: string | undefined; if ($settings) { - if ($settings.orderbooks?.[network]) { - orderbookAddress = $settings.orderbooks[network].address as Hex; + if ($settings.orderbook.orderbooks?.[network]) { + orderbookAddress = $settings.orderbook.orderbooks[network].address as Hex; } - if ($settings.subgraphs) { - subgraphUrl = $settings.subgraphs[network].url; + if ($settings.orderbook.subgraphs) { + subgraphUrl = $settings.orderbook.subgraphs[network].url; } - if ($settings.networks?.[network]) { - rpcUrl = $settings.networks[network].rpc; + if ($settings.orderbook.networks?.[network]) { + rpcUrl = $settings.orderbook.networks[network].rpc; } } diff --git a/tauri-app/src/routes/orders/add/+page.svelte b/tauri-app/src/routes/orders/add/+page.svelte index 2dce68f1d..c8fc3c509 100644 --- a/tauri-app/src/routes/orders/add/+page.svelte +++ b/tauri-app/src/routes/orders/add/+page.svelte @@ -45,7 +45,7 @@ let composedRainlangForScenarios: Map = new Map(); - $: deployments = mergedConfig?.deployments; + $: deployments = mergedConfig?.dotrainOrder.deployments; $: deployment = deploymentRef ? deployments?.[deploymentRef] : undefined; // Resetting the selected deployment to undefined if it is not in the current @@ -75,7 +75,10 @@ error, } = useDebouncedFn(generateRainlangStrings, 500); - $: debouncedGenerateRainlangStrings($globalDotrainFile.text, mergedConfig?.scenarios); + $: debouncedGenerateRainlangStrings( + $globalDotrainFile.text, + mergedConfig?.dotrainOrder.scenarios, + ); $: rainlangExtension = new RawRainlangExtension({ diagnostics: async (text) => { From 91386db8031d04eece4f1dce8ead250cad3e838e Mon Sep 17 00:00:00 2001 From: findolor Date: Mon, 28 Apr 2025 11:02:13 +0300 Subject: [PATCH 41/51] update test --- .../orders/[network]-[orderHash]/page.test.ts | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/tauri-app/src/routes/orders/[network]-[orderHash]/page.test.ts b/tauri-app/src/routes/orders/[network]-[orderHash]/page.test.ts index 237c3b80e..560767ae6 100644 --- a/tauri-app/src/routes/orders/[network]-[orderHash]/page.test.ts +++ b/tauri-app/src/routes/orders/[network]-[orderHash]/page.test.ts @@ -57,32 +57,34 @@ describe('Order Page', () => { it('renders OrderDetail when all settings are available', () => { mockSettingsStore.mockSetSubscribeValue({ - orderbooks: { - ethereum: { - key: 'ethereum', - network: { + orderbook: { + orderbooks: { + ethereum: { key: 'ethereum', - rpc: 'https://ethereum.example.com', - chainId: 1, + network: { + key: 'ethereum', + rpc: 'https://ethereum.example.com', + chainId: 1, + }, + address: '0xabc', + subgraph: { + key: 'ethereum', + url: 'https://api.thegraph.com/subgraphs/name/example', + }, }, - address: '0xabc', - subgraph: { + }, + subgraphs: { + ethereum: { key: 'ethereum', url: 'https://api.thegraph.com/subgraphs/name/example', }, }, - }, - subgraphs: { - ethereum: { - key: 'ethereum', - url: 'https://api.thegraph.com/subgraphs/name/example', - }, - }, - networks: { - ethereum: { - key: 'ethereum', - rpc: 'https://ethereum.example.com', - chainId: 1, + networks: { + ethereum: { + key: 'ethereum', + rpc: 'https://ethereum.example.com', + chainId: 1, + }, }, }, } as unknown as Config); @@ -95,7 +97,7 @@ describe('Order Page', () => { describe('Missing settings tests', () => { beforeEach(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any - mockSettingsStore.mockSetSubscribeValue({} as any); + mockSettingsStore.mockSetSubscribeValue({ orderbook: {} } as any); }); it('displays error when all settings are missing', () => { @@ -113,14 +115,16 @@ describe('Order Page', () => { it('only displays actually missing items', async () => { // Set partial settings mockSettingsStore.mockSetSubscribeValue({ - orderbooks: { - ethereum: { - address: '0xabc', + orderbook: { + orderbooks: { + ethereum: { + address: '0xabc', + }, }, - }, - networks: { - ethereum: { - rpc: 'https://ethereum.example.com', + networks: { + ethereum: { + rpc: 'https://ethereum.example.com', + }, }, }, // eslint-disable-next-line @typescript-eslint/no-explicit-any From 95f4e651851d70d8c638447783c094f33ce230d8 Mon Sep 17 00:00:00 2001 From: findolor Date: Mon, 28 Apr 2025 11:33:33 +0300 Subject: [PATCH 42/51] fix tests --- .../ListViewOrderbookFilters.test.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts b/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts index 499226a9c..57e987abb 100644 --- a/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts +++ b/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts @@ -21,19 +21,21 @@ type ListViewOrderbookFiltersProps = ComponentProps { const mockSettings = writable({ - networks: { - ethereum: { - key: 'ethereum', - rpc: 'https://rpc.ankr.com/eth', - chainId: 1, - networkId: 1, - currency: 'ETH' - } - }, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'mainnet-url' + orderbook: { + networks: { + ethereum: { + key: 'ethereum', + rpc: 'https://rpc.ankr.com/eth', + chainId: 1, + networkId: 1, + currency: 'ETH' + } + }, + subgraphs: { + mainnet: { + key: 'mainnet', + url: 'mainnet-url' + } } } } as unknown as Config); From 4c6107fd0b8d34baf3ae7c13987a3a7097b4e925 Mon Sep 17 00:00:00 2001 From: findolor Date: Mon, 28 Apr 2025 12:43:52 +0300 Subject: [PATCH 43/51] update tests --- .../ListViewOrderbookFilters.test.ts | 30 ++++++------- packages/webapp/src/routes/+layout.ts | 42 +------------------ tauri-app/src/lib/services/pickConfig.ts | 6 +-- 3 files changed, 20 insertions(+), 58 deletions(-) diff --git a/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts b/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts index 57e987abb..6648a3129 100644 --- a/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts +++ b/packages/ui-components/src/__tests__/ListViewOrderbookFilters.test.ts @@ -55,26 +55,28 @@ describe('ListViewOrderbookFilters', () => { beforeEach(() => { mockSettings.set({ - networks: { - ethereum: { - key: 'ethereum', - rpc: 'https://rpc.ankr.com/eth', - chainId: 1, - networkId: 1, - currency: 'ETH' - } - }, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'mainnet-url' + orderbook: { + networks: { + ethereum: { + key: 'ethereum', + rpc: 'https://rpc.ankr.com/eth', + chainId: 1, + networkId: 1, + currency: 'ETH' + } + }, + subgraphs: { + mainnet: { + key: 'mainnet', + url: 'mainnet-url' + } } } } as unknown as Config); }); test('shows no networks alert when networks are empty', () => { - mockSettings.set({ networks: {}, subgraphs: {} } as unknown as Config); + mockSettings.set({ orderbook: { networks: {}, subgraphs: {} } } as unknown as Config); render(ListViewOrderbookFilters, defaultProps); expect(screen.getByTestId('no-networks-alert')).toBeInTheDocument(); diff --git a/packages/webapp/src/routes/+layout.ts b/packages/webapp/src/routes/+layout.ts index 38442a6a6..b7fd84c85 100644 --- a/packages/webapp/src/routes/+layout.ts +++ b/packages/webapp/src/routes/+layout.ts @@ -41,7 +41,7 @@ export const load = async ({ fetch }) => { const activeAccountsItems = writable>({}); const subgraph = derived([settings, activeOrderbook], ([$settings, $activeOrderbook]) => - $settings?.subgraphs !== undefined && $activeOrderbook?.subgraph !== undefined + $settings.subgraphs !== undefined && $activeOrderbook?.subgraph !== undefined ? $settings.subgraphs[$activeOrderbook.subgraph.key] : undefined ); @@ -411,45 +411,5 @@ subgraphs: expect(finalAccounts).toHaveProperty('account1'); expect(finalAccounts).toHaveProperty('account2'); }); - - it('should handle partial or invalid data in settings correctly', async () => { - const partialSettings = ` -networks: - network1: - rpc: https://network1.rpc - chainId: 1 - label: Network 1 - currency: ETH -subgraphs: - subgraph1: - key: subgraph1 - url: https://subgraph1.url -orderbooks: - orderbook1: - address: 0x1234567890123456789012345678901234567890 - network: network1 - subgraph: non-existent-subgraph - orderbook2: - address: 0x1234567890123456789012345678901234567890 - network: network1 -`; - - vi.mocked(parseYaml).mockReturnValue(partialSettings as unknown as Config); - mockFetch.mockResolvedValueOnce({ - text: () => Promise.resolve(partialSettings) - }); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const result = await load({ fetch: mockFetch } as any); - const { stores } = result; - - stores.activeOrderbookRef.set('orderbook1'); - expect(get(stores.activeOrderbook)).toBeUndefined(); - expect(get(stores.subgraph)).toBeUndefined(); - - stores.activeOrderbookRef.set('orderbook2'); - expect(get(stores.activeOrderbook)).toBeUndefined(); - expect(get(stores.subgraph)).toBeUndefined(); - }); }); } diff --git a/tauri-app/src/lib/services/pickConfig.ts b/tauri-app/src/lib/services/pickConfig.ts index ee1a6bc85..600f39533 100644 --- a/tauri-app/src/lib/services/pickConfig.ts +++ b/tauri-app/src/lib/services/pickConfig.ts @@ -1,11 +1,11 @@ -import { pickBy, isNil } from 'lodash'; +import { pickBy } from 'lodash'; import type { Config } from '@rainlanguage/orderbook'; export function pickDeployments( mergedConfig: Config | undefined, activeNetworkRef: string | undefined, ) { - return mergedConfig?.dotrainOrder?.deployments && mergedConfig?.dotrainOrder?.orders + return mergedConfig?.dotrainOrder?.deployments ? pickBy( mergedConfig.dotrainOrder.deployments, (d) => @@ -19,7 +19,7 @@ export function pickScenarios( mergedConfig: Config | undefined, activeNetworkRef: string | undefined, ) { - return !isNil(mergedConfig) + return mergedConfig?.dotrainOrder.scenarios ? pickBy( mergedConfig.dotrainOrder.scenarios, (d) => d?.deployer?.network?.key === activeNetworkRef, From f48b56cec4a781d0db6226010ff72516ac9eafd1 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 7 May 2025 13:43:44 +0300 Subject: [PATCH 44/51] use strategies repo for settings yaml --- packages/webapp/src/routes/+layout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webapp/src/routes/+layout.ts b/packages/webapp/src/routes/+layout.ts index b7fd84c85..5a542e875 100644 --- a/packages/webapp/src/routes/+layout.ts +++ b/packages/webapp/src/routes/+layout.ts @@ -10,7 +10,7 @@ export interface LayoutData { export const load = async ({ fetch }) => { const response = await fetch( - 'https://raw.githubusercontent.com/findolor/sample-dotrains/6865d8c76dc4182c5f6f41c20299cb017a6298fe/settings.yaml' + 'https://github.com/rainlanguage/rain.strategies/blob/246947a82cbc12a9cb6bbfa07b4743d0ec5e5ff2/settings.yaml' ); const settingsYaml = await response.text(); @@ -199,7 +199,7 @@ subgraphs: const result = await load({ fetch: mockFetch } as any); expect(mockFetch).toHaveBeenCalledWith( - 'https://raw.githubusercontent.com/findolor/sample-dotrains/6865d8c76dc4182c5f6f41c20299cb017a6298fe/settings.yaml' + 'https://github.com/rainlanguage/rain.strategies/blob/246947a82cbc12a9cb6bbfa07b4743d0ec5e5ff2/settings.yaml' ); expect(result).toHaveProperty('stores'); From 700fe77bfb1fcd1834ad9cd0574e7106bdb65be4 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 7 May 2025 19:24:13 +0300 Subject: [PATCH 45/51] add helper methods and tests --- .../dropdown/DropdownOrderListAccounts.svelte | 5 +-- .../components/tables/VaultsListTable.svelte | 18 ++------ .../src/lib/utils/configHelpers.ts | 44 +++++++++++++++++++ 3 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 packages/ui-components/src/lib/utils/configHelpers.ts diff --git a/packages/ui-components/src/lib/components/dropdown/DropdownOrderListAccounts.svelte b/packages/ui-components/src/lib/components/dropdown/DropdownOrderListAccounts.svelte index c2c5c6fb3..2e772352e 100644 --- a/packages/ui-components/src/lib/components/dropdown/DropdownOrderListAccounts.svelte +++ b/packages/ui-components/src/lib/components/dropdown/DropdownOrderListAccounts.svelte @@ -2,11 +2,10 @@ import type { AccountCfg } from '@rainlanguage/orderbook'; import DropdownCheckbox from './DropdownCheckbox.svelte'; import type { Writable, Readable } from 'svelte/store'; + import { getAccountsAsOptions } from '$lib/utils/configHelpers'; export let accounts: Readable> | undefined; export let activeAccountsItems: Writable> | undefined; - $: options = Object.fromEntries( - Object.entries($accounts ?? {}).map(([key, value]) => [key, value.address]) - ); + $: options = getAccountsAsOptions($accounts ?? {});
diff --git a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte index 6e8846cb2..4f5010da2 100644 --- a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte +++ b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte @@ -1,4 +1,6 @@ + +
+ +
diff --git a/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte b/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte index f31bc01f3..f4cd08e3d 100644 --- a/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte +++ b/packages/ui-components/src/lib/components/ListViewOrderbookFilters.svelte @@ -1,9 +1,10 @@ @@ -51,7 +53,9 @@ {#if isOrdersPage} - +
+ +
{/if} {#if $accounts && Object.values($accounts).length > 0} diff --git a/packages/ui-components/src/lib/components/checkbox/CheckboxActiveOrders.svelte b/packages/ui-components/src/lib/components/checkbox/CheckboxActiveOrders.svelte new file mode 100644 index 000000000..9ac53d3ee --- /dev/null +++ b/packages/ui-components/src/lib/components/checkbox/CheckboxActiveOrders.svelte @@ -0,0 +1,12 @@ + + +
+ Include inactive orders +
diff --git a/packages/ui-components/src/lib/components/detail/TanstackOrderQuote.svelte b/packages/ui-components/src/lib/components/detail/TanstackOrderQuote.svelte index 0e5dad55c..f01a0d531 100644 --- a/packages/ui-components/src/lib/components/detail/TanstackOrderQuote.svelte +++ b/packages/ui-components/src/lib/components/detail/TanstackOrderQuote.svelte @@ -54,7 +54,7 @@
-

Order Quotes

+

Order quotes

{#if $orderQuoteQuery.data && $orderQuoteQuery.data.length > 0 && isHex($orderQuoteQuery.data[0].blockNumber)} - Owner Address + Owner address diff --git a/packages/ui-components/src/lib/components/dropdown/DropdownOrderStatus.svelte b/packages/ui-components/src/lib/components/dropdown/DropdownOrderStatus.svelte deleted file mode 100644 index f3675bf20..000000000 --- a/packages/ui-components/src/lib/components/dropdown/DropdownOrderStatus.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - -
- -
diff --git a/packages/ui-components/src/lib/components/input/InputOrderHash.svelte b/packages/ui-components/src/lib/components/input/InputOrderHash.svelte index b560cdd4e..4896bbed4 100644 --- a/packages/ui-components/src/lib/components/input/InputOrderHash.svelte +++ b/packages/ui-components/src/lib/components/input/InputOrderHash.svelte @@ -12,7 +12,7 @@
- +
- +
diff --git a/packages/ui-components/src/lib/components/input/InputToken.svelte b/packages/ui-components/src/lib/components/input/InputToken.svelte index 935674c55..ef48c76ea 100644 --- a/packages/ui-components/src/lib/components/input/InputToken.svelte +++ b/packages/ui-components/src/lib/components/input/InputToken.svelte @@ -42,10 +42,10 @@
{#if !isAddressValid && address.length > 0} - Invalid Address + Invalid address {/if} - Token Address + Token address
; - - $: isVaultsPage = currentRoute.startsWith('/vaults'); - $: isOrdersPage = currentRoute.startsWith('/orders'); - Vault Balance Changes + Vault balance changes Date diff --git a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte index 4f5010da2..42e6b6240 100644 --- a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte +++ b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte @@ -44,7 +44,6 @@ undefined; export let handleWithdrawModal: ((vault: SgVault, refetch: () => void) => void) | undefined = undefined; - export let currentRoute: string; export let showMyItemsOnly: AppStoresInterface['showMyItemsOnly']; const { account, matchesAccount } = useAccount(); @@ -88,10 +87,6 @@ activeNetworkRef.set(subgraphName); activeOrderbookRef.set(subgraphName); }; - - $: isVaultsPage = currentRoute.startsWith('/vaults'); - $: isOrdersPage = currentRoute.startsWith('/orders'); - const AppTable = TanstackAppTable; @@ -105,8 +100,6 @@ {activeOrderStatus} {orderHash} {hideZeroBalanceVaults} - {isVaultsPage} - {isOrdersPage} /> {truncateEthAddress($account)} {:else} - Connect Wallet + Connect {/if} diff --git a/packages/ui-components/src/lib/index.ts b/packages/ui-components/src/lib/index.ts index 4f8ec26ae..a754968a3 100644 --- a/packages/ui-components/src/lib/index.ts +++ b/packages/ui-components/src/lib/index.ts @@ -7,7 +7,7 @@ export { default as DropdownCheckbox } from './components/dropdown/DropdownCheck export { default as DropdownOrderListAccounts } from './components/dropdown/DropdownOrderListAccounts.svelte'; export { default as DropdownRadio } from './components/dropdown/DropdownRadio.svelte'; export { default as Refresh } from './components/icon/Refresh.svelte'; -export { default as DropdownOrderStatus } from './components/dropdown/DropdownOrderStatus.svelte'; +export { default as CheckboxActiveOrders } from './components/checkbox/CheckboxActiveOrders.svelte'; export { default as InputOrderHash } from './components/input/InputOrderHash.svelte'; export { default as CheckboxZeroBalanceVault } from './components/CheckboxZeroBalanceVault.svelte'; export { default as ListViewOrderbookFilters } from './components/ListViewOrderbookFilters.svelte'; @@ -116,6 +116,12 @@ export { lightCodeMirrorTheme, darkCodeMirrorTheme } from './utils/codeMirrorThe // Stores export { default as transactionStore } from './stores/transactionStore'; +export { + cachedWritableStore, + cachedWritableIntOptional, + cachedWritableStringOptional, + cachedWritableString +} from './storesGeneric/cachedWritableStore'; // Assets export { default as logoLight } from './assets/logo-light.svg'; diff --git a/packages/ui-components/src/lib/services/awaitTransactionIndexing.ts b/packages/ui-components/src/lib/services/awaitTransactionIndexing.ts new file mode 100644 index 000000000..09cb6029f --- /dev/null +++ b/packages/ui-components/src/lib/services/awaitTransactionIndexing.ts @@ -0,0 +1,283 @@ +/** + * @module awaitTransactionIndexing + * @description Utilities for waiting for transactions to be indexed by a subgraph + */ + +import type { + SgAddOrderWithOrder, + SgRemoveOrderWithOrder, + SgTransaction +} from '@rainlanguage/orderbook'; +import { + getTransaction, + getTransactionAddOrders, + getTransactionRemoveOrders +} from '@rainlanguage/orderbook'; + +/** + * Error message when subgraph indexing times out + */ +export const TIMEOUT_ERROR = + 'The subgraph took too long to respond. Your transaction may still be processing.'; + +/** + * Result of a subgraph indexing operation + * @template T The type of data returned by the subgraph + */ +export type IndexingResult = { + /** + * The successful result of the indexing operation + */ + value?: { + /** + * The transaction hash + */ + txHash: string; + /** + * Message to display on successful indexing + */ + successMessage: string; + /** + * Optional order hash if available + */ + orderHash?: string; + /** + * Optional network key + */ + network?: string; + /** + * Optional data returned from the subgraph + */ + data?: T; + }; + /** + * Error message if indexing failed + */ + error?: string; +}; + +/** + * Generic function to handle waiting for subgraph indexing + * Returns a promise that resolves with an object containing either value or error + * + * @template T The type of data returned by the subgraph + * @param options Configuration options for the indexing operation + * @returns Promise resolving to an IndexingResult + */ +export const awaitSubgraphIndexing = async (options: { + /** + * URL of the subgraph to query + */ + subgraphUrl: string; + /** + * Transaction hash to check for indexing + */ + txHash: string; + /** + * Message to display on successful indexing + */ + successMessage: string; + /** + * Maximum number of attempts before timing out + */ + maxAttempts?: number; + /** + * Interval between attempts in milliseconds + */ + interval?: number; + /** + * Optional network identifier + */ + network?: string; + /** + * Function to fetch data from the subgraph + * @param subgraphUrl URL of the subgraph + * @param txHash Transaction hash to query + */ + fetchData: (subgraphUrl: string, txHash: string) => Promise; + /** + * Function to determine if the fetched data indicates success + * @param data The data returned from the subgraph + */ + isSuccess: (data: T) => boolean; +}): Promise> => { + const { + subgraphUrl, + txHash, + successMessage, + maxAttempts = 10, + interval = 1000, + network, + fetchData, + isSuccess + } = options; + + return new Promise((resolve) => { + let attempts = 0; + + const checkInterval = setInterval(async () => { + attempts++; + try { + const data = await fetchData(subgraphUrl, txHash); + + if (data && isSuccess(data)) { + clearInterval(checkInterval); + + let orderHash; + // Extract orderHash from order data if it exists in the expected format + if (Array.isArray(data) && data.length > 0 && data[0]?.order?.orderHash) { + orderHash = data[0].order.orderHash; + } + + resolve({ + value: { + txHash, + successMessage, + orderHash, + network, + data + } + }); + + return; + } + } catch { + // Continue with the next attempt + } + + if (attempts >= maxAttempts) { + clearInterval(checkInterval); + resolve({ + error: TIMEOUT_ERROR + }); + return; + } + }, interval); + }); +}; + +/** + * Configuration for transaction indexing + * @template T The type of data returned by the subgraph + */ +export interface TransactionConfig { + /** + * URL of the subgraph to query + */ + subgraphUrl: string; + /** + * Transaction hash to check for indexing + */ + txHash: string; + /** + * Message to display on successful indexing + */ + successMessage: string; + /** + * Optional network key + */ + network?: string; + /** + * Maximum number of attempts before timing out + */ + maxAttempts?: number; + /** + * Interval between attempts in milliseconds + */ + interval?: number; + /** + * Function to fetch data from the subgraph + */ + fetchData: (subgraphUrl: string, txHash: string) => Promise; + /** + * Function to determine if the fetched data indicates success + */ + isSuccess: (data: T) => boolean; +} + +/** + * Creates a configuration for checking general transaction indexing + * + * @param subgraphUrl URL of the subgraph to query + * @param txHash Transaction hash to check for indexing + * @param successMessage Message to display on successful indexing + * @param network Optional network key + * @param maxAttempts Maximum number of attempts before timing out + * @param interval Interval between attempts in milliseconds + * @returns Configuration for transaction indexing + */ +export const getTransactionConfig = ( + subgraphUrl: string, + txHash: string, + successMessage: string, + network?: string, + maxAttempts?: number, + interval?: number +): TransactionConfig => ({ + subgraphUrl, + txHash, + successMessage, + network, + maxAttempts, + interval, + fetchData: getTransaction, + isSuccess: (tx: SgTransaction) => !!tx +}); + +/** + * Creates a configuration for checking new order indexing + * + * @param subgraphUrl URL of the subgraph to query + * @param txHash Transaction hash to check for indexing + * @param successMessage Message to display on successful indexing + * @param network Optional network key + * @param maxAttempts Maximum number of attempts before timing out + * @param interval Interval between attempts in milliseconds + * @returns Configuration for new order indexing + */ +export const getNewOrderConfig = ( + subgraphUrl: string, + txHash: string, + successMessage: string, + network?: string, + maxAttempts?: number, + interval?: number +): TransactionConfig => ({ + subgraphUrl, + txHash, + successMessage, + network, + maxAttempts, + interval, + fetchData: getTransactionAddOrders, + isSuccess: (addOrders: SgAddOrderWithOrder[]) => addOrders?.length > 0 +}); + +/** + * Creates a configuration for checking order removal indexing + * + * @param subgraphUrl URL of the subgraph to query + * @param txHash Transaction hash to check for indexing + * @param successMessage Message to display on successful indexing + * @param network Optional network key + * @param maxAttempts Maximum number of attempts before timing out + * @param interval Interval between attempts in milliseconds + * @returns Configuration for order removal indexing + */ +export const getRemoveOrderConfig = ( + subgraphUrl: string, + txHash: string, + successMessage: string, + network?: string, + maxAttempts?: number, + interval?: number +): TransactionConfig => ({ + subgraphUrl, + txHash, + successMessage, + network, + maxAttempts, + interval, + fetchData: getTransactionRemoveOrders, + isSuccess: (removeOrders: SgRemoveOrderWithOrder[]) => removeOrders?.length > 0 +}); diff --git a/packages/ui-components/src/lib/stores/transactionStore.ts b/packages/ui-components/src/lib/stores/transactionStore.ts index 6deb4b668..3ebc05c5c 100644 --- a/packages/ui-components/src/lib/stores/transactionStore.ts +++ b/packages/ui-components/src/lib/stores/transactionStore.ts @@ -5,18 +5,19 @@ import { sendTransaction, switchChain, waitForTransactionReceipt } from '@wagmi/ import type { ApprovalCalldata, DepositCalldataResult, - SgTransaction, RemoveOrderCalldata, SgVault, WithdrawCalldataResult } from '@rainlanguage/orderbook'; -import { - getTransaction, - getTransactionAddOrders, - getTransactionRemoveOrders -} from '@rainlanguage/orderbook'; + import { getExplorerLink } from '../services/getExplorerLink'; import type { DeploymentArgs } from '$lib/types/transaction'; +import { + awaitSubgraphIndexing, + getNewOrderConfig, + getRemoveOrderConfig, + getTransactionConfig +} from '$lib/services/awaitTransactionIndexing'; export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000'; export const ONE = BigInt('1000000000000000000'); @@ -38,7 +39,7 @@ export enum TransactionStatus { export enum TransactionErrorMessage { BAD_CALLLDATA = 'Bad calldata.', DEPLOY_FAILED = 'Lock transaction failed.', - TIMEOUT = 'The subgraph took too long to respond. Please check the transaction link.', + TIMEOUT = 'The subgraph took too long to respond.', APPROVAL_FAILED = 'Approval transaction failed.', USER_REJECTED_APPROVAL = 'User rejected approval transaction.', USER_REJECTED_TRANSACTION = 'User rejected the transaction.', @@ -115,11 +116,6 @@ const transactionStore = () => { const { subscribe, set, update } = writable(initialState); const reset = () => set(initialState); - const returnError = (interval: NodeJS.Timeout) => { - clearInterval(interval); - return transactionError(TransactionErrorMessage.TIMEOUT); - }; - const awaitTransactionIndexing = async ( subgraphUrl: string, txHash: string, @@ -128,31 +124,20 @@ const transactionStore = () => { update((state) => ({ ...state, status: TransactionStatus.PENDING_SUBGRAPH, - message: 'Checking for transaction indexing...' + message: 'Waiting for transaction to be indexed...' })); - let attempts = 0; - let newTx: SgTransaction; + const result = await awaitSubgraphIndexing( + getTransactionConfig(subgraphUrl, txHash, successMessage) + ); - const interval: NodeJS.Timeout = setInterval(async () => { - attempts++; + if (result.error) { + return transactionError(TransactionErrorMessage.TIMEOUT); + } - try { - newTx = await getTransaction(subgraphUrl, txHash); - if (newTx) { - clearInterval(interval); - transactionSuccess(txHash, successMessage); - } else if (attempts >= 10) { - update((state) => ({ - ...state, - message: 'The subgraph took too long to respond. Please check again later.' - })); - return returnError(interval); - } - } catch { - return returnError(interval); - } - }, 1000); + if (result.value) { + return transactionSuccess(result.value.txHash, result.value.successMessage); + } }; const awaitNewOrderIndexing = async (subgraphUrl: string, txHash: string, network?: string) => { @@ -162,21 +147,20 @@ const transactionStore = () => { message: 'Waiting for new order to be indexed...' })); - let attempts = 0; - const interval: NodeJS.Timeout = setInterval(async () => { - attempts++; - try { - const addOrders = await getTransactionAddOrders(subgraphUrl, txHash); - if (attempts >= 10) { - return returnError(interval); - } else if (addOrders?.length > 0) { - clearInterval(interval); - return transactionSuccess(txHash, '', addOrders[0].order.orderHash, network); - } - } catch { - return returnError(interval); - } - }, 1000); + const result = await awaitSubgraphIndexing(getNewOrderConfig(subgraphUrl, txHash, '', network)); + + if (result.error) { + return transactionError(TransactionErrorMessage.TIMEOUT); + } + + if (result.value) { + return transactionSuccess( + result.value.txHash, + result.value.successMessage, + result.value.orderHash, + result.value.network + ); + } }; const awaitRemoveOrderIndexing = async (subgraphUrl: string, txHash: string) => { @@ -186,25 +170,17 @@ const transactionStore = () => { message: 'Waiting for order removal to be indexed...' })); - let attempts = 0; - const interval: NodeJS.Timeout = setInterval(async () => { - attempts++; - try { - const removeOrders = await getTransactionRemoveOrders(subgraphUrl, txHash); - if (attempts >= 10) { - update((state) => ({ - ...state, - message: 'The subgraph took too long to respond. Please check again later.' - })); - return returnError(interval); - } else if (removeOrders?.length > 0) { - clearInterval(interval); - return transactionSuccess(txHash, 'Order removed successfully'); - } - } catch { - return returnError(interval); - } - }, 1000); + const result = await awaitSubgraphIndexing( + getRemoveOrderConfig(subgraphUrl, txHash, 'Order removed successfully') + ); + + if (result.error) { + return transactionError(TransactionErrorMessage.TIMEOUT); + } + + if (result.value) { + return transactionSuccess(result.value.txHash, result.value.successMessage); + } }; const checkingWalletAllowance = (message?: string) => @@ -370,11 +346,13 @@ const transactionStore = () => { return transactionError(TransactionErrorMessage.USER_REJECTED_TRANSACTION); } try { + const transactionExplorerLink = await getExplorerLink(hash, chainId, 'tx'); awaitTx( hash, action === 'deposit' ? TransactionStatus.PENDING_DEPOSIT - : TransactionStatus.PENDING_WITHDRAWAL + : TransactionStatus.PENDING_WITHDRAWAL, + transactionExplorerLink ); await waitForTransactionReceipt(config, { hash }); return awaitTransactionIndexing( @@ -414,9 +392,9 @@ const transactionStore = () => { } catch { return transactionError(TransactionErrorMessage.USER_REJECTED_TRANSACTION); } - try { - awaitTx(hash, TransactionStatus.PENDING_REMOVE_ORDER); + const transactionExplorerLink = await getExplorerLink(hash, chainId, 'tx'); + awaitTx(hash, TransactionStatus.PENDING_REMOVE_ORDER, transactionExplorerLink); await waitForTransactionReceipt(config, { hash }); return awaitRemoveOrderIndexing(subgraphUrl, hash); } catch { diff --git a/packages/ui-components/src/lib/storesGeneric/cachedWritableStore.ts b/packages/ui-components/src/lib/storesGeneric/cachedWritableStore.ts new file mode 100644 index 000000000..760cb7bf4 --- /dev/null +++ b/packages/ui-components/src/lib/storesGeneric/cachedWritableStore.ts @@ -0,0 +1,137 @@ +import { writable } from 'svelte/store'; + +/** + * Creates a writable Svelte store that persists its value to localStorage. + * + * @template T - The type of the value stored in the store + * @param {string} key - The localStorage key used to store the value + * @param {T} defaultValue - The default value to use when no value is found in localStorage + * @param {function(T): string} serialize - Function to convert the store value to a string for storage + * @param {function(string): T} deserialize - Function to convert the stored string back to the original type + * @returns {import('svelte/store').Writable} A writable store that automatically syncs with localStorage + * + * @example + * // Create a store for a boolean value + * const darkMode = cachedWritableStore( + * 'darkMode', + * false, + * value => JSON.stringify(value), + * str => JSON.parse(str) + * ); + * + * // Create a store for a complex object + * const userPreferences = cachedWritableStore( + * 'userPrefs', + * { theme: 'light', fontSize: 14 }, + * value => JSON.stringify(value), + * str => JSON.parse(str) + * ); + */ +export function cachedWritableStore( + key: string, + defaultValue: T, + serialize: (value: T) => string, + deserialize: (serialized: string) => T +) { + const getCache = () => { + try { + const cached = localStorage.getItem(key); + return cached !== null ? deserialize(cached) : defaultValue; + } catch { + return defaultValue; + } + }; + const setCache = (value?: T) => { + try { + if (value !== undefined) { + localStorage.setItem(key, serialize(value)); + } else { + localStorage.removeItem(key); + } + } catch { + // Silently ignore localStorage errors to allow the application to function + // without persistence in environments where localStorage is unavailable + } + }; + + const data = writable(getCache()); + + data.subscribe((value) => { + setCache(value); + }); + + return data; +} + +export const cachedWritableString = (key: string, defaultValue = '') => + cachedWritableStore( + key, + defaultValue, + (v) => v, + (v) => v + ); +export const cachedWritableInt = (key: string, defaultValue = 0) => + cachedWritableStore( + key, + defaultValue, + (v) => v.toString(), + (v) => { + const parsed = Number.parseInt(v); + return isNaN(parsed) ? defaultValue : parsed; + } + ); +/** + * Creates a writable store that can hold an optional value of type T and persists to localStorage. + * + * @template T - The type of the value stored + * @param {string} key - The localStorage key to use for persistence + * @param {T | undefined} defaultValue - The default value if nothing is found in localStorage + * @param {function} serialize - Function to convert the value to a string for storage + * @param {function} deserialize - Function to convert the stored string back to a value + * @returns A writable store that persists to localStorage and can hold undefined values + */ +export const cachedWritableOptionalStore = ( + key: string, + defaultValue: T | undefined = undefined, + serialize: (value: T) => string, + deserialize: (serialized: string) => T +) => + cachedWritableStore( + key, + defaultValue, + (v) => (v !== undefined ? serialize(v) : ''), + (v) => (v !== '' ? deserialize(v) : undefined) + ); + +/** + * Creates a writable store that can hold an optional number value and persists to localStorage. + * + * @param {string} key - The localStorage key to use for persistence + * @param {number | undefined} defaultValue - The default value if nothing is found in localStorage + * @returns A writable store that persists to localStorage and can hold an optional number + */ +export const cachedWritableIntOptional = (key: string, defaultValue = undefined) => + cachedWritableOptionalStore( + key, + defaultValue, + (v) => v.toString(), + (v) => { + const parsed = Number.parseInt(v); + return isNaN(parsed) ? (defaultValue ?? 0) : parsed; + } + ); + +/** + * Creates a writable store that can hold an optional string value and persists to localStorage. + * + * @param {string} key - The localStorage key to use for persistence + * @param {string | undefined} defaultValue - The default value if nothing is found in localStorage + * @returns A writable store that persists to localStorage and can hold an optional string + */ +export const cachedWritableStringOptional = (key: string, defaultValue = undefined) => + cachedWritableOptionalStore( + key, + defaultValue, + (v) => v, + (v) => v + ); diff --git a/packages/ui-components/test-setup.ts b/packages/ui-components/test-setup.ts index cda5d98de..e12c9cf0c 100644 --- a/packages/ui-components/test-setup.ts +++ b/packages/ui-components/test-setup.ts @@ -66,7 +66,6 @@ vi.mock('@rainlanguage/orderbook', () => { DotrainOrderGui.prototype.getCurrentDeployment = vi.fn(); DotrainOrderGui.prototype.getVaultIds = vi.fn(); DotrainOrderGui.prototype.saveDeposit = vi.fn(); - DotrainOrderGui.prototype.isDepositPreset = vi.fn(); DotrainOrderGui.prototype.getDeposits = vi.fn(); DotrainOrderGui.prototype.saveFieldValue = vi.fn(); DotrainOrderGui.prototype.getFieldValue = vi.fn(); diff --git a/packages/webapp/env.example b/packages/webapp/.env.example similarity index 100% rename from packages/webapp/env.example rename to packages/webapp/.env.example diff --git a/packages/webapp/src/__tests__/TransactionModal.test.ts b/packages/webapp/src/__tests__/TransactionModal.test.ts index a81c15694..54e5d2056 100644 --- a/packages/webapp/src/__tests__/TransactionModal.test.ts +++ b/packages/webapp/src/__tests__/TransactionModal.test.ts @@ -176,7 +176,7 @@ describe('TransactionModal Component', () => { render(TransactionModal, { props: { open: true, messages } }); await waitFor(() => { - const viewOrderButton = screen.getByText('View Order'); + const viewOrderButton = screen.getByText('View order'); expect(viewOrderButton).toBeInTheDocument(); expect(viewOrderButton.closest('a')).toHaveAttribute( 'href', diff --git a/packages/webapp/src/lib/__mocks__/MockComponent.svelte b/packages/webapp/src/lib/__mocks__/MockComponent.svelte index 47d0e6eb2..042218e0c 100644 --- a/packages/webapp/src/lib/__mocks__/MockComponent.svelte +++ b/packages/webapp/src/lib/__mocks__/MockComponent.svelte @@ -4,3 +4,5 @@ // whenever the props update, we want to update the store with those $: $props = $$props; + + diff --git a/packages/webapp/src/lib/__mocks__/stores.ts b/packages/webapp/src/lib/__mocks__/stores.ts index 16f1815f4..e4e359401 100644 --- a/packages/webapp/src/lib/__mocks__/stores.ts +++ b/packages/webapp/src/lib/__mocks__/stores.ts @@ -3,6 +3,24 @@ import { type Config } from '@wagmi/core'; import { mockWeb3Config } from './mockWeb3Config'; import type { AppKit } from '@reown/appkit'; +export const initialPageState = { + data: { + stores: { settings: {} }, + dotrain: 'some dotrain content', + deployment: { key: 'deploy-key' }, + strategyDetail: {} + }, + url: new URL('http://localhost:3000/deploy'), + params: {}, + form: {}, + status: 200, + error: null, + route: { + id: null + } +}; + +const mockPageWritable = writable(initialPageState); const mockSignerAddressWritable = writable(''); const mockChainIdWritable = writable(0); const mockConnectedWritable = writable(false); @@ -38,3 +56,9 @@ export const mockAppKitModalStore = { set: mockAppKitModalWritable.set, mockSetSubscribeValue: (value: AppKit): void => mockAppKitModalWritable.set(value) }; + +export const mockPageStore = { + subscribe: mockPageWritable.subscribe, + set: mockPageWritable.set, + mockSetSubscribeValue: (value: typeof initialPageState): void => mockPageWritable.set(value) +}; diff --git a/packages/webapp/src/lib/components/Homepage.svelte b/packages/webapp/src/lib/components/Homepage.svelte index e2ca75dd4..fb7ff0d99 100644 --- a/packages/webapp/src/lib/components/Homepage.svelte +++ b/packages/webapp/src/lib/components/Homepage.svelte @@ -6,7 +6,7 @@ export let colorTheme; -
+
diff --git a/packages/webapp/src/lib/components/TransactionModal.svelte b/packages/webapp/src/lib/components/TransactionModal.svelte index 78d96226f..06f8203bf 100644 --- a/packages/webapp/src/lib/components/TransactionModal.svelte +++ b/packages/webapp/src/lib/components/TransactionModal.svelte @@ -71,7 +71,7 @@
{#if $transactionStore.newOrderHash && $transactionStore.network} - + {/if} diff --git a/packages/webapp/src/lib/index.ts b/packages/webapp/src/lib/index.ts deleted file mode 100644 index 856f2b6c3..000000000 --- a/packages/webapp/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/packages/webapp/src/lib/stores/settings.ts b/packages/webapp/src/lib/stores/settings.ts new file mode 100644 index 000000000..c0aca67e6 --- /dev/null +++ b/packages/webapp/src/lib/stores/settings.ts @@ -0,0 +1,23 @@ +import { cachedWritableStore } from '@rainlanguage/ui-components'; + +/** + * A persistent store that controls whether vaults with zero balance should be hidden in the UI. + * + * This setting is saved to local storage and persists between sessions. + * + * @default true - Zero balance vaults are hidden by default + * @returns A writable store containing a boolean value + */ +export const hideZeroBalanceVaults = cachedWritableStore( + 'settings.hideZeroBalanceVaults', + true, // default value is true + (value) => JSON.stringify(value), + (str) => { + try { + const value = JSON.parse(str); + return typeof value === 'boolean' ? value : true; + } catch { + return true; + } + } +); diff --git a/packages/webapp/src/routes/+layout.svelte b/packages/webapp/src/routes/+layout.svelte index 4cfff14c3..f91a5062d 100644 --- a/packages/webapp/src/routes/+layout.svelte +++ b/packages/webapp/src/routes/+layout.svelte @@ -24,14 +24,21 @@ } }); + let walletInitError: string | null = null; + const initWallet = async () => { - const erckit = defaultConfig({ - appName: 'Rain Language', - connectors: [injected(), walletConnect({ projectId: PUBLIC_WALLETCONNECT_PROJECT_ID })], - chains: supportedChainsList as unknown as Chain[], - projectId: PUBLIC_WALLETCONNECT_PROJECT_ID - }); - await erckit.init(); + try { + const erckit = defaultConfig({ + appName: 'Rain Language', + connectors: [injected(), walletConnect({ projectId: PUBLIC_WALLETCONNECT_PROJECT_ID })], + chains: supportedChainsList as unknown as Chain[], + projectId: PUBLIC_WALLETCONNECT_PROJECT_ID + }); + await erckit.init(); + walletInitError = null; + } catch (error) { + walletInitError = `Failed to initialize wallet connection: ${error instanceof Error ? error.message : 'Unknown error'}. Please try again or check console.`; + } }; $: if (browser && window.navigator) { @@ -39,6 +46,14 @@ } +{#if walletInitError} +
+ {walletInitError} +
+{/if} + @@ -46,6 +61,7 @@ {:else}
diff --git a/packages/webapp/src/routes/+layout.ts b/packages/webapp/src/routes/+layout.ts index 5a542e875..ff50929f8 100644 --- a/packages/webapp/src/routes/+layout.ts +++ b/packages/webapp/src/routes/+layout.ts @@ -62,7 +62,8 @@ export const load = async ({ fetch }) => { accounts, activeAccountsItems, activeAccounts, - activeOrderStatus: writable(undefined), + // Instantiate with false to show only active orders + activeOrderStatus: writable(false), orderHash: writable(''), hideZeroBalanceVaults: writable(false), activeNetworkRef, diff --git a/packages/webapp/src/routes/layout.test.ts b/packages/webapp/src/routes/layout.test.ts new file mode 100644 index 000000000..32272cf59 --- /dev/null +++ b/packages/webapp/src/routes/layout.test.ts @@ -0,0 +1,139 @@ +import { render, waitFor, screen } from '@testing-library/svelte'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import Layout from './+layout.svelte'; + +const { mockPageStore, initialPageState } = await vi.hoisted(() => import('$lib/__mocks__/stores')); + +const mockErcKit = vi.hoisted(() => ({ + init: vi.fn().mockResolvedValue(undefined) +})); +const mockDefaultConfig = vi.hoisted(() => vi.fn().mockReturnValue(mockErcKit)); +const mockEnv = vi.hoisted(() => ({ browser: true })); + +vi.mock('$app/stores', async (importOriginal) => { + return { + ...((await importOriginal()) as object), + page: mockPageStore + }; +}); + +vi.mock('$app/environment', () => mockEnv); + +vi.mock('../lib/components/Sidebar.svelte', async () => { + const MockSidebar = (await import('@rainlanguage/ui-components')).MockComponent; + return { default: MockSidebar }; +}); + +vi.mock('@rainlanguage/ui-components', async (importOriginal) => { + const MockWalletProvider = (await import('../lib/__mocks__/MockComponent.svelte')).default; + return { + ...(await importOriginal()), + + WalletProvider: MockWalletProvider + }; +}); + +vi.mock('$lib/stores/wagmi', () => ({ + defaultConfig: mockDefaultConfig, + signerAddress: { subscribe: vi.fn() } +})); + +vi.mock('$env/static/public', () => ({ + PUBLIC_WALLETCONNECT_PROJECT_ID: 'test-project-id' +})); + +vi.mock('@wagmi/connectors', async (importOriginal) => { + return { + ...(await importOriginal()), + + injected: vi.fn().mockReturnValue('injected-connector'), + walletConnect: vi.fn().mockReturnValue('wallet-connect-connector') + }; +}); + +describe('Layout component', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.resetAllMocks(); + mockDefaultConfig.mockReturnValue(mockErcKit); + mockEnv.browser = true; + }); + + it('initializes wallet when in browser environment', async () => { + const originalNavigator = global.navigator; + + Object.defineProperty(global, 'navigator', { + value: {}, + writable: true + }); + mockPageStore.mockSetSubscribeValue(initialPageState); + + render(Layout); + + expect(mockErcKit.init).toHaveBeenCalled(); + Object.defineProperty(global, 'navigator', { + value: originalNavigator, + writable: true + }); + }); + + it('displays an error message if wallet initialization fails', async () => { + const originalNavigator = global.navigator; + Object.defineProperty(global, 'navigator', { + value: {}, + writable: true + }); + + mockErcKit.init.mockRejectedValue(new Error('Initialization failed')); + mockPageStore.mockSetSubscribeValue(initialPageState); + + render(Layout); + + const errorMessage = await screen.findByText( + 'Failed to initialize wallet connection: Initialization failed. Please try again or check console.' + ); + expect(errorMessage).toBeInTheDocument(); + + Object.defineProperty(global, 'navigator', { + value: originalNavigator, + writable: true + }); + }); + + it('renders Homepage when on root path', async () => { + mockPageStore.mockSetSubscribeValue({ + ...initialPageState, + url: new URL('http://localhost/') + }); + + const { container } = render(Layout); + + expect(container.querySelector('main')).not.toBeInTheDocument(); + expect(screen.getByTestId('homepage')).toBeInTheDocument(); + }); + + it('renders Sidebar and main content when not on root path', async () => { + mockPageStore.mockSetSubscribeValue({ + ...initialPageState, + url: new URL('http://localhost/some-page') + }); + + render(Layout); + + await waitFor(() => { + expect(screen.getByTestId('layout-container')).toBeInTheDocument(); + expect(screen.getByTestId('mock-component')).toBeInTheDocument(); + }); + }); + + it('does not initialize wallet when not in browser environment', async () => { + const originalNavigator = global.navigator; + mockEnv.browser = false; + render(Layout); + expect(mockErcKit.init).not.toHaveBeenCalled(); + Object.defineProperty(global, 'navigator', { + value: originalNavigator, + writable: true + }); + }); +}); diff --git a/packages/webapp/src/routes/orders/+page.svelte b/packages/webapp/src/routes/orders/+page.svelte index f09da3cea..b3709ba89 100644 --- a/packages/webapp/src/routes/orders/+page.svelte +++ b/packages/webapp/src/routes/orders/+page.svelte @@ -18,7 +18,6 @@ showMyItemsOnly = writable(false) }: AppStoresInterface = $page.data.stores; - $: currentRoute = $page.url.pathname; $: showMyItemsOnly.set($connected); @@ -35,5 +34,4 @@ {activeOrderStatus} {orderHash} {hideZeroBalanceVaults} - {currentRoute} /> diff --git a/packages/webapp/src/routes/vaults/+page.svelte b/packages/webapp/src/routes/vaults/+page.svelte index 239b79e52..a83ff75cd 100644 --- a/packages/webapp/src/routes/vaults/+page.svelte +++ b/packages/webapp/src/routes/vaults/+page.svelte @@ -4,22 +4,22 @@ import { page } from '$app/stores'; import { connected } from '$lib/stores/wagmi'; import { writable } from 'svelte/store'; + import { hideZeroBalanceVaults } from '$lib/stores/settings'; const { activeOrderbook, subgraph, orderHash, - activeSubgraphs, settings, accounts, activeAccountsItems, activeOrderStatus, - hideZeroBalanceVaults, activeNetworkRef, activeOrderbookRef, activeAccounts, activeNetworkOrderbooks, - showMyItemsOnly = writable(false) + showMyItemsOnly = writable(false), + activeSubgraphs } = $page.data.stores; export async function resetActiveNetworkRef() { @@ -68,5 +68,4 @@ {activeNetworkRef} {activeOrderbookRef} {activeAccounts} - currentRoute={$page.url.pathname} /> diff --git a/tauri-app/src/lib/__mocks__/stores.ts b/tauri-app/src/lib/__mocks__/stores.ts new file mode 100644 index 000000000..88c8daf4a --- /dev/null +++ b/tauri-app/src/lib/__mocks__/stores.ts @@ -0,0 +1,9 @@ +import { writable } from 'svelte/store'; + +const mockColorThemeWritable = writable('light'); + +export const mockColorThemeStore = { + subscribe: mockColorThemeWritable.subscribe, + set: mockColorThemeWritable.set, + mockSetSubscribeValue: (value: string): void => mockColorThemeWritable.set(value), +}; diff --git a/tauri-app/src/lib/test/matchers.ts b/tauri-app/src/lib/__tests__/matchers.ts similarity index 100% rename from tauri-app/src/lib/test/matchers.ts rename to tauri-app/src/lib/__tests__/matchers.ts diff --git a/tauri-app/src/lib/components/WordTable.test.ts b/tauri-app/src/lib/components/WordTable.test.ts index 2a15ba150..8cd048bc3 100644 --- a/tauri-app/src/lib/components/WordTable.test.ts +++ b/tauri-app/src/lib/components/WordTable.test.ts @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/svelte'; import { test } from 'vitest'; import WordTable from './WordTable.svelte'; import type { AuthoringMetaV2 } from '@rainlanguage/orderbook'; -import { expect } from '$lib/test/matchers'; +import { expect } from '$lib/__tests__/matchers'; import userEvent from '@testing-library/user-event'; const authoringMeta: AuthoringMetaV2 = { diff --git a/tauri-app/src/lib/components/Words.test.ts b/tauri-app/src/lib/components/Words.test.ts index 3ee27fcb5..887bb7480 100644 --- a/tauri-app/src/lib/components/Words.test.ts +++ b/tauri-app/src/lib/components/Words.test.ts @@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/svelte'; import { test } from 'vitest'; import Words from './Words.svelte'; import type { ScenarioWords } from '@rainlanguage/orderbook'; -import { expect } from '$lib/test/matchers'; +import { expect } from '$lib/__tests__/matchers'; import userEvent from '@testing-library/user-event'; const authoringMetas: ScenarioWords[] = [ diff --git a/tauri-app/src/lib/components/debug/EvalResultsTable.test.ts b/tauri-app/src/lib/components/debug/EvalResultsTable.test.ts index 599dcaed3..2c38b4eca 100644 --- a/tauri-app/src/lib/components/debug/EvalResultsTable.test.ts +++ b/tauri-app/src/lib/components/debug/EvalResultsTable.test.ts @@ -1,6 +1,6 @@ import { render, screen, waitFor } from '@testing-library/svelte'; import { test } from 'vitest'; -import { expect } from '$lib/test/matchers'; +import { expect } from '$lib/__tests__/matchers'; import EvalResultsTable from './EvalResultsTable.svelte'; import type { RainEvalResultsTable } from '@rainlanguage/orderbook'; import { formatEther, hexToBigInt, isHex } from 'viem'; diff --git a/tauri-app/src/lib/components/modal/ModalQuoteDebug.test.ts b/tauri-app/src/lib/components/modal/ModalQuoteDebug.test.ts index e1b1bf055..43bd6d86a 100644 --- a/tauri-app/src/lib/components/modal/ModalQuoteDebug.test.ts +++ b/tauri-app/src/lib/components/modal/ModalQuoteDebug.test.ts @@ -1,6 +1,6 @@ import { render, screen, waitFor } from '@testing-library/svelte'; import { test } from 'vitest'; -import { expect } from '$lib/test/matchers'; +import { expect } from '$lib/__tests__/matchers'; import { mockIPC } from '@tauri-apps/api/mocks'; import { QueryClient } from '@tanstack/svelte-query'; import { formatEther } from 'viem'; diff --git a/tauri-app/src/lib/components/modal/ModalTradeDebug.test.ts b/tauri-app/src/lib/components/modal/ModalTradeDebug.test.ts index 8646fd149..77d538f4c 100644 --- a/tauri-app/src/lib/components/modal/ModalTradeDebug.test.ts +++ b/tauri-app/src/lib/components/modal/ModalTradeDebug.test.ts @@ -1,6 +1,6 @@ import { render, screen, waitFor } from '@testing-library/svelte'; import { test } from 'vitest'; -import { expect } from '$lib/test/matchers'; +import { expect } from '$lib/__tests__/matchers'; import { mockIPC } from '@tauri-apps/api/mocks'; import ModalTradeDebug from './ModalTradeDebug.svelte'; import { QueryClient } from '@tanstack/svelte-query'; diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts deleted file mode 100644 index db9346d01..000000000 --- a/tauri-app/src/lib/stores/settings.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { expect, test, beforeEach, describe } from 'vitest'; -import { settings, activeAccountsItems, activeSubgraphs, EMPTY_SETTINGS } from './settings'; -import { get } from 'svelte/store'; -import type { Config, NetworkCfg, SubgraphCfg } from '@rainlanguage/orderbook'; - -// Define the mock directly in the tests -const mockConfig: Config = { - orderbook: { - networks: { - mainnet: { - key: 'mainnet', - rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', - chainId: 1, - label: 'Ethereum Mainnet', - currency: 'ETH', - }, - }, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'https://api.thegraph.com/subgraphs/name/mainnet', - }, - }, - orderbooks: { - orderbook1: { - key: 'orderbook1', - address: '0xOrderbookAddress1', - network: { - key: 'mainnet', - } as unknown as NetworkCfg, - subgraph: { - key: 'uniswap', - } as unknown as SubgraphCfg, - label: 'Orderbook 1', - }, - }, - deployers: { - deployer1: { - key: 'deployer1', - address: '0xDeployerAddress1', - network: { - key: 'mainnet', - } as unknown as NetworkCfg, - }, - }, - metaboards: { - metaboard1: 'https://example.com/metaboard1', - }, - accounts: { - name_one: 'address_one', - name_two: 'address_two', - }, - }, -} as unknown as Config; - -describe('Settings active accounts items', () => { - // Reset store values before each test to prevent state leakage - beforeEach(() => { - // Reset all store values - settings.set(EMPTY_SETTINGS); - activeAccountsItems.set({}); - activeSubgraphs.set({}); - - // Then set our initial test values - settings.set(mockConfig); - activeAccountsItems.set({ - name_one: 'address_one', - name_two: 'address_two', - }); - activeSubgraphs.set({ - mainnet: { - key: 'mainnet', - url: 'https://api.thegraph.com/subgraphs/name/mainnet', - }, - }); - - // Verify initial state - expect(get(settings)).toEqual(mockConfig); - expect(get(activeAccountsItems)).toEqual({ - name_one: 'address_one', - name_two: 'address_two', - }); - expect(get(activeSubgraphs)).toEqual({ - mainnet: { - key: 'mainnet', - url: 'https://api.thegraph.com/subgraphs/name/mainnet', - }, - }); - }); - - test('should remove account if that account is removed', () => { - // Test removing an account - const newSettings = { - orderbook: { - ...mockConfig.orderbook, - accounts: { - name_one: { - key: 'name_one', - address: 'address_one', - }, - }, - }, - dotrainOrder: mockConfig.dotrainOrder, - }; - - // Update settings - this should trigger the subscription - settings.set(newSettings); - - // Check the expected result - expect(get(activeAccountsItems)).toEqual({ - name_one: 'address_one', - }); - }); - - test('should remove account if the value is different', () => { - const newSettings = { - orderbook: { - ...mockConfig.orderbook, - accounts: { - name_one: { - key: 'name_one', - address: 'address_one', - }, - name_two: { - key: 'name_two', - address: 'new_value', - }, - }, - }, - dotrainOrder: mockConfig.dotrainOrder, - }; - - settings.set(newSettings); - - expect(get(activeAccountsItems)).toEqual({ - name_one: 'address_one', - }); - }); - - test('should update active subgraphs when subgraph value changes', () => { - const newSettings = { - orderbook: { - ...mockConfig.orderbook, - subgraphs: { - mainnet: { - key: 'mainnet', - url: 'new value', - }, - }, - }, - dotrainOrder: mockConfig.dotrainOrder, - }; - - settings.set(newSettings as unknown as Config); - - expect(get(activeSubgraphs)).toEqual({ - mainnet: { - key: 'mainnet', - url: 'new value', - }, - }); - }); - - test('should update active subgraphs when subgraph removed', () => { - const newSettings = { - orderbook: { - ...mockConfig.orderbook, - subgraphs: { - testnet: { - key: 'testnet', - url: 'testnet', - }, - }, - }, - dotrainOrder: mockConfig.dotrainOrder, - }; - - settings.set(newSettings as unknown as Config); - - expect(get(activeSubgraphs)).toEqual({}); - }); - - test('should reset active subgraphs when subgraphs are undefined', () => { - const newSettings = { - orderbook: { - ...mockConfig.orderbook, - subgraphs: undefined, - }, - dotrainOrder: mockConfig.dotrainOrder, - }; - - settings.set(newSettings as unknown as Config); - - expect(get(activeSubgraphs)).toEqual({}); - }); -}); diff --git a/tauri-app/src/lib/stores/settings.ts b/tauri-app/src/lib/stores/settings.ts index c887feac3..812c65fde 100644 --- a/tauri-app/src/lib/stores/settings.ts +++ b/tauri-app/src/lib/stores/settings.ts @@ -1,8 +1,5 @@ import { asyncDerived, derived, get } from '@square/svelte-store'; -import { - cachedWritableStore, - cachedWritableStringOptional, -} from '$lib/storesGeneric/cachedWritableStore'; +import { cachedWritableStore, cachedWritableStringOptional } from '@rainlanguage/ui-components'; import find from 'lodash/find'; import * as chains from 'viem/chains'; import { textFileStore } from '$lib/storesGeneric/textFileStore'; @@ -14,6 +11,7 @@ import { } from '@rainlanguage/orderbook'; import { getBlockNumberFromRpc } from '$lib/services/chain'; import { pickBy } from 'lodash'; +import { beforeEach, describe } from 'vitest'; export const EMPTY_SETTINGS: Config = { orderbook: { @@ -295,3 +293,200 @@ export const orderHash = cachedWritableStore( (value) => value, (str) => str || '', ); + +if (import.meta.vitest) { + const { test, expect } = import.meta.vitest; + + const mockConfig: Config = { + orderbook: { + networks: { + mainnet: { + key: 'mainnet', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH', + }, + }, + subgraphs: { + mainnet: { + key: 'mainnet', + url: 'https://api.thegraph.com/subgraphs/name/mainnet', + }, + }, + orderbooks: { + orderbook1: { + key: 'orderbook1', + address: '0xOrderbookAddress1', + network: { + key: 'mainnet', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH', + }, + subgraph: { + key: 'uniswap', + url: 'https://api.thegraph.com/subgraphs/name/mainnet', + }, + }, + }, + tokens: {}, + deployers: { + deployer1: { + key: 'deployer1', + address: '0xDeployerAddress1', + network: { + key: 'mainnet', + rpc: 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', + chainId: 1, + label: 'Ethereum Mainnet', + currency: 'ETH', + }, + }, + }, + metaboards: { + metaboard1: 'https://example.com/metaboard1', + }, + accounts: { + name_one: { + key: 'name_one', + address: 'address_one', + }, + name_two: { + key: 'name_two', + address: 'address_two', + }, + }, + }, + dotrainOrder: { + orders: {}, + scenarios: {}, + charts: {}, + deployments: {}, + }, + }; + + describe('Settings active accounts items', () => { + // Reset store values before each test to prevent state leakage + beforeEach(() => { + // Reset all store values + settings.set(EMPTY_SETTINGS); + activeAccountsItems.set({}); + activeSubgraphs.set({}); + + // Then set our initial test values + settings.set(mockConfig); + activeAccountsItems.set({ + name_one: 'address_one', + name_two: 'address_two', + }); + activeSubgraphs.set({ + mainnet: { + key: 'mainnet', + url: 'https://api.thegraph.com/subgraphs/name/mainnet', + }, + }); + + // Verify initial state + expect(get(settings)).toEqual(mockConfig); + expect(get(activeAccountsItems)).toEqual({ + name_one: 'address_one', + name_two: 'address_two', + }); + expect(get(activeSubgraphs)).toEqual({ + mainnet: { + key: 'mainnet', + url: 'https://api.thegraph.com/subgraphs/name/mainnet', + }, + }); + }); + + test('should remove account if that account is removed', () => { + // Test removing an account + const newSettings = { + ...mockConfig, + orderbook: { + accounts: { + name_one: 'address_one', + }, + }, + } as unknown as Config; + + // Update settings - this should trigger the subscription + settings.set(newSettings); + + // Check the expected result + expect(get(activeAccountsItems)).toEqual({ + name_one: 'address_one', + }); + }); + + test('should remove account if the value is different', () => { + const newSettings = { + ...mockConfig, + orderbook: { + accounts: { + name_one: 'address_one', + name_two: 'new_value', + }, + }, + } as unknown as Config; + + settings.set(newSettings); + + expect(get(activeAccountsItems)).toEqual({ + name_one: 'address_one', + }); + }); + + test('should update active subgraphs when subgraph value changes', () => { + const newSettings = { + ...mockConfig, + orderbook: { + subgraphs: { + mainnet: { + key: 'mainnet', + url: 'https://api.thegraph.com/subgraphs/name/mainnet', + }, + }, + }, + } as unknown as Config; + + settings.set(newSettings); + + expect(get(activeSubgraphs)).toEqual({}); + }); + + test('should update active subgraphs when subgraph removed', () => { + const newSettings = { + ...mockConfig, + orderbook: { + subgraphs: { + testnet: { + key: 'testnet', + url: 'testnet', + }, + }, + }, + } as unknown as Config; + + settings.set(newSettings); + + expect(get(activeSubgraphs)).toEqual({}); + }); + + test('should reset active subgraphs when subgraphs are undefined', () => { + const newSettings = { + ...mockConfig, + orderbook: { + subgraphs: undefined, + }, + } as unknown as Config; + + settings.set(newSettings); + + expect(get(activeSubgraphs)).toEqual({}); + }); + }); +} diff --git a/tauri-app/src/lib/storesGeneric/cachedWritableStore.ts b/tauri-app/src/lib/storesGeneric/cachedWritableStore.ts deleted file mode 100644 index ea8a32949..000000000 --- a/tauri-app/src/lib/storesGeneric/cachedWritableStore.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { writable } from 'svelte/store'; - -export function cachedWritableStore( - key: string, - defaultValue: T, - serialize: (value: T) => string, - deserialize: (serialized: string) => T, -) { - const getCache = () => { - const cached = localStorage.getItem(key); - return cached !== null ? deserialize(cached) : defaultValue; - }; - const setCache = (value?: T) => { - if (value !== undefined) { - localStorage.setItem(key, serialize(value)); - } else { - localStorage.removeItem(key); - } - }; - - const data = writable(getCache()); - - data.subscribe((value) => { - setCache(value); - }); - - return data; -} - -export const cachedWritableString = (key: string, defaultValue = '') => - cachedWritableStore( - key, - defaultValue, - (v) => v, - (v) => v, - ); -export const cachedWritableInt = (key: string, defaultValue = 0) => - cachedWritableStore( - key, - defaultValue, - (v) => v.toString(), - (v) => parseInt(v), - ); - -export const cachedWritableOptionalStore = ( - key: string, - defaultValue: T | undefined = undefined, - serialize: (value: T) => string, - deserialize: (serialized: string) => T, -) => - cachedWritableStore( - key, - defaultValue, - (v) => (v ? serialize(v) : ''), - (v) => (v ? deserialize(v) : undefined), - ); - -export const cachedWritableIntOptional = (key: string, defaultValue = undefined) => - cachedWritableOptionalStore( - key, - defaultValue, - (v) => v.toString(), - (v) => parseInt(v), - ); -export const cachedWritableStringOptional = (key: string, defaultValue = undefined) => - cachedWritableOptionalStore( - key, - defaultValue, - (v) => v, - (v) => v, - ); diff --git a/tauri-app/src/lib/storesGeneric/detailStore.ts b/tauri-app/src/lib/storesGeneric/detailStore.ts index af70c104b..fff53afd6 100644 --- a/tauri-app/src/lib/storesGeneric/detailStore.ts +++ b/tauri-app/src/lib/storesGeneric/detailStore.ts @@ -1,5 +1,5 @@ import { toasts } from '$lib/stores/toasts'; -import { cachedWritableStore } from '$lib/storesGeneric/cachedWritableStore'; +import { cachedWritableStore } from '@rainlanguage/ui-components'; import { derived, writable, diff --git a/tauri-app/src/lib/storesGeneric/fetchableStore.ts b/tauri-app/src/lib/storesGeneric/fetchableStore.ts index 79a8f31e6..74c772a77 100644 --- a/tauri-app/src/lib/storesGeneric/fetchableStore.ts +++ b/tauri-app/src/lib/storesGeneric/fetchableStore.ts @@ -1,5 +1,5 @@ import { derived, writable } from 'svelte/store'; -import { cachedWritableStore } from './cachedWritableStore'; +import { cachedWritableStore } from '@rainlanguage/ui-components'; import { toasts } from '$lib/stores/toasts'; import { reportErrorToSentry } from '$lib/services/sentry'; diff --git a/tauri-app/src/lib/storesGeneric/listStore.ts b/tauri-app/src/lib/storesGeneric/listStore.ts index fce5e42c9..ddf8b8704 100644 --- a/tauri-app/src/lib/storesGeneric/listStore.ts +++ b/tauri-app/src/lib/storesGeneric/listStore.ts @@ -3,7 +3,7 @@ import { toasts } from '../stores/toasts'; import { save } from '@tauri-apps/api/dialog'; import dayjs from 'dayjs'; import { ToastMessageType } from '../types/tauriBindings'; -import { cachedWritableStore } from '$lib/storesGeneric/cachedWritableStore'; +import { cachedWritableStore } from '@rainlanguage/ui-components'; import { flatten } from 'lodash'; import { reportErrorToSentry, SentrySeverityLevel } from '$lib/services/sentry'; diff --git a/tauri-app/src/lib/storesGeneric/settingStore.ts b/tauri-app/src/lib/storesGeneric/settingStore.ts index 4b5984835..4f798a284 100644 --- a/tauri-app/src/lib/storesGeneric/settingStore.ts +++ b/tauri-app/src/lib/storesGeneric/settingStore.ts @@ -1,5 +1,5 @@ import { derived } from 'svelte/store'; -import { cachedWritableString } from '$lib/storesGeneric/cachedWritableStore'; +import { cachedWritableString } from '@rainlanguage/ui-components'; interface ValidatedSetting { value: T; diff --git a/tauri-app/src/routes/+page.svelte b/tauri-app/src/routes/+page.svelte index 3fd0ba4b6..2c52a9bd7 100644 --- a/tauri-app/src/routes/+page.svelte +++ b/tauri-app/src/routes/+page.svelte @@ -6,22 +6,30 @@
- Raindex logo -
+ Raindex logo +
Raindex allows anyone to write, deploy and manage token trading strategies, written in Rainlang, on any EVM network.
- - Get started