diff --git a/crates/settings/src/deployer.rs b/crates/settings/src/deployer.rs index dac3eb5af..0dfb9991d 100644 --- a/crates/settings/src/deployer.rs +++ b/crates/settings/src/deployer.rs @@ -10,7 +10,8 @@ use strict_yaml_rust::StrictYaml; use thiserror::Error; use typeshare::typeshare; use yaml::{ - default_document, optional_string, require_hash, require_string, YamlError, YamlParsableHash, + context::Context, default_document, optional_string, require_hash, require_string, YamlError, + YamlParsableHash, }; #[cfg(target_family = "wasm")] @@ -101,6 +102,7 @@ impl DeployerConfigSource { impl YamlParsableHash for Deployer { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut deployers = HashMap::new(); @@ -123,7 +125,7 @@ impl YamlParsableHash for Deployer { Some(network_name) => network_name, None => deployer_key.clone(), }; - let network = Network::parse_from_yaml(documents.clone(), &network_name)?; + let network = Network::parse_from_yaml(documents.clone(), &network_name, None)?; let deployer = Deployer { document: document.clone(), @@ -236,7 +238,7 @@ deployers: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let deployers = Deployer::parse_all_from_yaml(documents).unwrap(); + let deployers = Deployer::parse_all_from_yaml(documents, None).unwrap(); assert_eq!(deployers.len(), 2); assert!(deployers.contains_key("DeployerOne")); @@ -272,7 +274,7 @@ deployers: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let error = Deployer::parse_all_from_yaml(documents).unwrap_err(); + let error = Deployer::parse_all_from_yaml(documents, None).unwrap_err(); assert_eq!( error, diff --git a/crates/settings/src/deployment.rs b/crates/settings/src/deployment.rs index d4c034356..8053382cf 100644 --- a/crates/settings/src/deployment.rs +++ b/crates/settings/src/deployment.rs @@ -7,7 +7,9 @@ use std::{ use strict_yaml_rust::StrictYaml; use thiserror::Error; use typeshare::typeshare; -use yaml::{default_document, require_hash, require_string, YamlError, YamlParsableHash}; +use yaml::{ + context::Context, default_document, require_hash, require_string, YamlError, YamlParsableHash, +}; #[cfg(target_family = "wasm")] use rain_orderbook_bindings::{impl_all_wasm_traits, wasm_traits::prelude::*}; @@ -31,6 +33,7 @@ impl_all_wasm_traits!(Deployment); impl YamlParsableHash for Deployment { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut deployments = HashMap::new(); @@ -41,26 +44,30 @@ impl YamlParsableHash for Deployment { for (key_yaml, deployment_yaml) in deployments_hash { let deployment_key = key_yaml.as_str().unwrap_or_default().to_string(); - let scenario = Scenario::parse_from_yaml( + let order = Order::parse_from_yaml( documents.clone(), &require_string( deployment_yaml, - Some("scenario"), + Some("order"), Some(format!( - "scenario string missing in deployment: {deployment_key}" + "order string missing in deployment: {deployment_key}" )), )?, + None, )?; - let order = Order::parse_from_yaml( + let context = Context::with_order(Arc::new(order.clone())); + + let scenario = Scenario::parse_from_yaml( documents.clone(), &require_string( deployment_yaml, - Some("order"), + Some("scenario"), Some(format!( - "order string missing in deployment: {deployment_key}" + "scenario string missing in deployment: {deployment_key}" )), )?, + Some(&context), )?; if let Some(deployer) = &order.deployer { @@ -238,7 +245,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: deployments".to_string()) @@ -249,10 +256,10 @@ deployments: deployment1: test: test "#; - let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, - YamlError::ParseError("scenario string missing in deployment: deployment1".to_string()) + YamlError::ParseError("order string missing in deployment: deployment1".to_string()) ); let yaml = r#" @@ -264,20 +271,26 @@ deployers: deployer1: address: 0x0000000000000000000000000000000000000000 network: network1 -scenarios: - scenario1: - bindings: - test: test +tokens: + token1: + address: 0x0000000000000000000000000000000000000000 + network: network1 +orders: + order1: + inputs: + - token: token1 + outputs: + - token: token1 deployer: deployer1 deployments: deployment1: - scenario: scenario1 + order: order1 test: test "#; - let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, - YamlError::ParseError("order string missing in deployment: deployment1".to_string()) + YamlError::ParseError("scenario string missing in deployment: deployment1".to_string()) ); let yaml = r#" @@ -316,7 +329,7 @@ deployments: scenario: scenario1 order: order1 "#; - let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error.to_string(), YamlError::ParseDeploymentConfigSourceError(ParseDeploymentConfigSourceError::NoMatch) @@ -385,10 +398,13 @@ deployments: order: order2 "#; - let deployments = Deployment::parse_all_from_yaml(vec![ - get_document(&format!("{PREFIX}{yaml_one}")), - get_document(yaml_two), - ]) + let deployments = Deployment::parse_all_from_yaml( + vec![ + get_document(&format!("{PREFIX}{yaml_one}")), + get_document(yaml_two), + ], + None, + ) .unwrap(); assert_eq!(deployments.len(), 2); @@ -420,10 +436,13 @@ deployments: order: order2 "#; - let error = Deployment::parse_all_from_yaml(vec![ - get_document(&format!("{PREFIX}{yaml_one}")), - get_document(yaml_two), - ]) + let error = Deployment::parse_all_from_yaml( + vec![ + get_document(&format!("{PREFIX}{yaml_one}")), + get_document(yaml_two), + ], + None, + ) .unwrap_err(); assert_eq!( diff --git a/crates/settings/src/gui.rs b/crates/settings/src/gui.rs index a9a407388..5b50b849a 100644 --- a/crates/settings/src/gui.rs +++ b/crates/settings/src/gui.rs @@ -1,7 +1,7 @@ use crate::{ yaml::{ - default_document, get_hash_value, optional_hash, optional_string, optional_vec, - require_string, require_vec, YamlError, YamlParsableHash, YamlParseableValue, + context::Context, default_document, get_hash_value, optional_hash, optional_string, + optional_vec, require_string, require_vec, YamlError, YamlParsableHash, YamlParseableValue, }, Deployment, Token, TokenRef, }; @@ -251,12 +251,16 @@ impl_all_wasm_traits!(Gui); impl Gui {} impl YamlParseableValue for Gui { - fn parse_from_yaml(_: Vec>>) -> Result { + fn parse_from_yaml( + _: Vec>>, + _: Option<&Context>, + ) -> Result { Err(YamlError::InvalidTraitFunction) } fn parse_from_yaml_optional( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut gui_res: Option = None; let mut gui_deployments_res: HashMap = HashMap::new(); @@ -303,7 +307,9 @@ impl YamlParseableValue for Gui { let deployment_name = deployment_name.as_str().unwrap_or_default().to_string(); let deployment = - Deployment::parse_from_yaml(documents.clone(), &deployment_name)?; + Deployment::parse_from_yaml(documents.clone(), &deployment_name, None)?; + + let context = Context::with_order(deployment.order.clone()); let name = require_string( deployment_yaml, @@ -334,7 +340,7 @@ impl YamlParseableValue for Gui { Some(format!( "token string missing for deposit index: {deposit_index} in gui deployment: {deployment_name}", )), - )?)?; + )?, None)?; let presets = require_vec( deposit_value, @@ -382,8 +388,10 @@ impl YamlParseableValue for Gui { "name string missing for field index: {field_index} in gui deployment: {deployment_name}", )), )?; + let interpolated_name = context.interpolate(&name)?; let description = optional_string(field_yaml, "description"); + let interpolated_description = description.map(|description| context.interpolate(&description)).transpose()?; let presets = match optional_vec(field_yaml, "presets") { Some(p) => Some(p.iter().enumerate().map(|(preset_index, preset_yaml)| { @@ -409,8 +417,8 @@ impl YamlParseableValue for Gui { let gui_field_definition = GuiFieldDefinition { binding, - name, - description, + name: interpolated_name, + description: interpolated_description, presets }; Ok(gui_field_definition) @@ -622,7 +630,7 @@ mod tests { gui: test: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("name field missing in gui".to_string()) @@ -632,7 +640,7 @@ gui: name: - test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("name field must be a string in gui".to_string()) @@ -642,7 +650,7 @@ gui: name: - test: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("name field must be a string in gui".to_string()) @@ -652,7 +660,7 @@ gui: gui: name: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("description field missing in gui".to_string()) @@ -663,7 +671,7 @@ gui: description: - test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("description field must be a string in gui".to_string()) @@ -674,7 +682,7 @@ gui: description: - test: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("description field must be a string in gui".to_string()) @@ -685,7 +693,7 @@ gui: name: test description: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("deployments field missing in gui".to_string()) @@ -696,7 +704,7 @@ gui: description: test deployments: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("deployments field must be a map in gui".to_string()) @@ -708,7 +716,7 @@ gui: deployments: - test: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("deployments field must be a map in gui".to_string()) @@ -722,7 +730,7 @@ gui: deployment1: test: test "#; - let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: deployments".to_string()) @@ -770,9 +778,11 @@ gui: deployment1: test: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError("name string missing in gui deployment: deployment1".to_string()) @@ -786,9 +796,11 @@ gui: deployment1: name: deployment1 "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -805,9 +817,11 @@ gui: name: some name description: some description "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -826,9 +840,11 @@ gui: deposits: - test: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -848,9 +864,11 @@ gui: deposits: - token: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!(error, YamlError::KeyNotFound("test".to_string())); let yaml = r#" @@ -864,9 +882,11 @@ gui: deposits: - token: token1 "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -888,9 +908,11 @@ gui: presets: - test: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -912,9 +934,11 @@ gui: presets: - "1" "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError("fields list missing in gui deployment: deployment1".to_string()) @@ -935,9 +959,11 @@ gui: fields: - test: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -961,9 +987,11 @@ gui: fields: - binding: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -990,9 +1018,11 @@ gui: - value: - test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -1021,9 +1051,11 @@ gui: select-tokens: - test: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(&format!("{yaml_prefix}{yaml}"))], + None, + ) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -1105,9 +1137,11 @@ gui: presets: - value: test "#; - let res = - Gui::parse_from_yaml_optional(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap(); + let res = Gui::parse_from_yaml_optional( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap(); let gui = res.unwrap(); assert_eq!(gui.deployments.len(), 2); @@ -1195,9 +1229,11 @@ gui: presets: - value: test "#; - let error = - Gui::parse_from_yaml_optional(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap_err(); + let error = Gui::parse_from_yaml_optional( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap_err(); assert_eq!(error, YamlError::KeyShadowing("deployment1".to_string())); } diff --git a/crates/settings/src/metaboard.rs b/crates/settings/src/metaboard.rs index dc726ee86..06383098e 100644 --- a/crates/settings/src/metaboard.rs +++ b/crates/settings/src/metaboard.rs @@ -1,4 +1,6 @@ -use crate::yaml::{default_document, require_hash, require_string, YamlError, YamlParsableHash}; +use crate::yaml::{ + context::Context, default_document, require_hash, require_string, YamlError, YamlParsableHash, +}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -68,6 +70,7 @@ impl Metaboard { impl YamlParsableHash for Metaboard { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut metaboards = HashMap::new(); @@ -143,7 +146,7 @@ metaboards: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let metaboards = Metaboard::parse_all_from_yaml(documents).unwrap(); + let metaboards = Metaboard::parse_all_from_yaml(documents, None).unwrap(); assert_eq!(metaboards.len(), 2); assert!(metaboards.contains_key("MetaboardOne")); @@ -171,7 +174,7 @@ metaboards: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let error = Metaboard::parse_all_from_yaml(documents).unwrap_err(); + let error = Metaboard::parse_all_from_yaml(documents, None).unwrap_err(); assert_eq!( error, diff --git a/crates/settings/src/network.rs b/crates/settings/src/network.rs index 296b6828b..b4bf9a3de 100644 --- a/crates/settings/src/network.rs +++ b/crates/settings/src/network.rs @@ -1,4 +1,5 @@ use crate::config_source::*; +use crate::yaml::context::Context; use crate::yaml::{ default_document, optional_string, require_hash, require_string, YamlError, YamlParsableHash, }; @@ -101,6 +102,7 @@ impl_all_wasm_traits!(Network); impl YamlParsableHash for Network { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut networks = HashMap::new(); @@ -232,7 +234,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Network::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Network::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: networks".to_string()) @@ -242,7 +244,7 @@ test: test networks: mainnet: "#; - let error = Network::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Network::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("rpc string missing in network: mainnet".to_string()) @@ -253,7 +255,7 @@ networks: mainnet: rpc: https://mainnet.infura.io "#; - let error = Network::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Network::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -282,9 +284,11 @@ networks: rpc: https://network-two.infura.io chain-id: 4 "#; - let networks = - Network::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap(); + let networks = Network::parse_all_from_yaml( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap(); assert_eq!(networks.len(), 4); assert_eq!( @@ -322,9 +326,11 @@ networks: rpc: https://mainnet.infura.io chain-id: 1 "#; - let error = - Network::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap_err(); + let error = Network::parse_all_from_yaml( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap_err(); assert_eq!(error, YamlError::KeyShadowing("mainnet".to_string())); } } diff --git a/crates/settings/src/order.rs b/crates/settings/src/order.rs index 31565b52d..d87918b19 100644 --- a/crates/settings/src/order.rs +++ b/crates/settings/src/order.rs @@ -10,8 +10,8 @@ use strict_yaml_rust::StrictYaml; use thiserror::Error; use typeshare::typeshare; use yaml::{ - default_document, optional_string, require_hash, require_string, require_vec, YamlError, - YamlParsableHash, + context::Context, default_document, optional_string, require_hash, require_string, require_vec, + YamlError, YamlParsableHash, }; #[cfg(target_family = "wasm")] @@ -227,6 +227,7 @@ impl Order { impl YamlParsableHash for Order { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut orders = HashMap::new(); @@ -244,6 +245,7 @@ impl YamlParsableHash for Order { let deployer = Arc::new(Deployer::parse_from_yaml( documents.clone(), &deployer_name, + None, )?); if let Some(n) = &network { if deployer.network != *n { @@ -264,6 +266,7 @@ impl YamlParsableHash for Order { let orderbook = Arc::new(Orderbook::parse_from_yaml( documents.clone(), &orderbook_name, + None, )?); if let Some(n) = &network { if orderbook.network != *n { @@ -294,7 +297,7 @@ impl YamlParsableHash for Order { "token string missing in input index: {i} in order: {order_key}" )), )?; - let token = Token::parse_from_yaml(documents.clone(), &token_name)?; + let token = Token::parse_from_yaml(documents.clone(), &token_name, None)?; if let Some(n) = &network { if token.network != *n { @@ -333,7 +336,7 @@ impl YamlParsableHash for Order { "token string missing in output index: {i} in order: {order_key}" )), )?; - let token = Token::parse_from_yaml(documents.clone(), &token_name)?; + let token = Token::parse_from_yaml(documents.clone(), &token_name, None)?; if let Some(n) = &network { if token.network != *n { @@ -700,7 +703,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: orders".to_string()) @@ -710,7 +713,7 @@ test: test orders: order1: "#; - let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("inputs list missing in order: order1".to_string()) @@ -722,7 +725,7 @@ orders: inputs: - test: test "#; - let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -736,7 +739,7 @@ orders: inputs: - token: eth "#; - let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: tokens".to_string()) @@ -756,7 +759,7 @@ orders: inputs: - token: eth "#; - let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("outputs list missing in order: order1".to_string()) @@ -778,7 +781,7 @@ orders: outputs: - test: test "#; - let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -818,7 +821,7 @@ orders: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let orders = Order::parse_all_from_yaml(documents).unwrap(); + let orders = Order::parse_all_from_yaml(documents, None).unwrap(); assert_eq!(orders.len(), 2); assert!(orders.contains_key("OrderOne")); @@ -859,7 +862,7 @@ orders: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let error = Order::parse_all_from_yaml(documents).unwrap_err(); + let error = Order::parse_all_from_yaml(documents, None).unwrap_err(); assert_eq!(error, YamlError::KeyShadowing("DuplicateOrder".to_string())); } diff --git a/crates/settings/src/orderbook.rs b/crates/settings/src/orderbook.rs index e8d092a5f..1272de7dd 100644 --- a/crates/settings/src/orderbook.rs +++ b/crates/settings/src/orderbook.rs @@ -9,6 +9,7 @@ use strict_yaml_rust::StrictYaml; use subgraph::Subgraph; use thiserror::Error; use typeshare::typeshare; +use yaml::context::Context; use yaml::{ default_document, optional_string, require_hash, require_string, YamlError, YamlParsableHash, }; @@ -38,6 +39,7 @@ impl Orderbook { impl YamlParsableHash for Orderbook { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut orderbooks = HashMap::new(); @@ -65,7 +67,7 @@ impl YamlParsableHash for Orderbook { Some(network_name) => network_name, None => orderbook_key.clone(), }; - let network = Network::parse_from_yaml(documents.clone(), &network_name)?; + let network = Network::parse_from_yaml(documents.clone(), &network_name, None)?; let subgraph_name = match optional_string(orderbook_yaml, "subgraph") { Some(subgraph_name) => subgraph_name, @@ -74,6 +76,7 @@ impl YamlParsableHash for Orderbook { let subgraph = Arc::new(Subgraph::parse_from_yaml( documents.clone(), &subgraph_name, + None, )?); let label = optional_string(orderbook_yaml, "label"); @@ -284,7 +287,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: orderbooks".to_string()) @@ -294,7 +297,7 @@ test: test orderbooks: TestOrderbook: "#; - let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("address string missing in orderbook: TestOrderbook".to_string()) @@ -306,7 +309,7 @@ orderbooks: address: 0x1234567890123456789012345678901234567890 network: TestNetwork "#; - let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: networks".to_string()) @@ -322,7 +325,7 @@ orderbooks: address: 0x1234567890123456789012345678901234567890 network: TestNetwork "#; - let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!(error, YamlError::KeyNotFound("TestNetwork".to_string())); let yaml = r#" @@ -335,7 +338,7 @@ orderbooks: address: 0x1234567890123456789012345678901234567890 network: TestNetwork "#; - let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: subgraphs".to_string()) @@ -354,7 +357,7 @@ orderbooks: network: TestNetwork subgraph: TestSubgraph "#; - let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!(error, YamlError::KeyNotFound("TestSubgraph".to_string())); } @@ -382,7 +385,7 @@ orderbooks: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let orderbooks = Orderbook::parse_all_from_yaml(documents).unwrap(); + let orderbooks = Orderbook::parse_all_from_yaml(documents, None).unwrap(); assert_eq!(orderbooks.len(), 2); assert!(orderbooks.contains_key("OrderbookOne")); @@ -422,7 +425,7 @@ orderbooks: "#; let documents = vec![get_document(yaml_one), get_document(yaml_two)]; - let error = Orderbook::parse_all_from_yaml(documents).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(documents, None).unwrap_err(); assert_eq!( error, diff --git a/crates/settings/src/scenario.rs b/crates/settings/src/scenario.rs index 46660eea4..ec9c3220e 100644 --- a/crates/settings/src/scenario.rs +++ b/crates/settings/src/scenario.rs @@ -10,8 +10,8 @@ use strict_yaml_rust::{strict_yaml::Hash, StrictYaml}; use thiserror::Error; use typeshare::typeshare; use yaml::{ - default_document, optional_hash, optional_string, require_hash, require_string, YamlError, - YamlParsableHash, + context::Context, default_document, optional_hash, optional_string, require_hash, + require_string, YamlError, YamlParsableHash, }; #[cfg(target_family = "wasm")] @@ -53,6 +53,7 @@ impl Scenario { } } + #[allow(clippy::too_many_arguments)] pub fn validate_scenario( documents: Vec>>, current_document: Arc>, @@ -61,6 +62,7 @@ impl Scenario { parent_scenario: ScenarioParent, scenario_key: String, scenario_yaml: &StrictYaml, + context: Option<&Context>, ) -> Result<(), YamlError> { let current_bindings = require_hash( scenario_yaml, @@ -70,15 +72,22 @@ impl Scenario { .iter() .map(|(binding_key, binding_value)| { let binding_key = binding_key.as_str().unwrap_or_default(); + let binding_value = require_string( + binding_value, + None, + Some(format!( + "binding value must be a string for key: {binding_key} in scenario: {scenario_key}", + )), + )?; + + let interpolated_value = match context { + Some(context) => context.interpolate(&binding_value)?, + None => binding_value.to_string(), + }; + Ok(( binding_key.to_string(), - require_string( - binding_value, - None, - Some(format!( - "binding value must be a string for key: {binding_key} in scenario: {scenario_key}", - )), - )?, + interpolated_value, )) }) .collect::, YamlError>>()?; @@ -96,7 +105,13 @@ impl Scenario { )); } } - bindings.insert(k.to_string(), v.to_string()); + + let binding_value = match context { + Some(context) => context.interpolate(&v)?, + None => v.to_string(), + }; + + bindings.insert(k.to_string(), binding_value); } let runs = optional_string(scenario_yaml, "runs") @@ -107,7 +122,8 @@ impl Scenario { .transpose()?; if let Some(deployer_name) = optional_string(scenario_yaml, "deployer") { - let current_deployer = Deployer::parse_from_yaml(documents.clone(), &deployer_name)?; + let current_deployer = + Deployer::parse_from_yaml(documents.clone(), &deployer_name, None)?; if let Some(parent_deployer) = parent_scenario.deployer.as_ref() { if current_deployer.key != parent_deployer.key { @@ -159,6 +175,7 @@ impl Scenario { }, child_key, child_scenario_yaml, + context, )?; } } @@ -275,13 +292,14 @@ impl Scenario { } } - Self::parse_from_yaml(vec![self.document.clone()], &self.key) + Self::parse_from_yaml(vec![self.document.clone()], &self.key, None) } } impl YamlParsableHash for Scenario { fn parse_all_from_yaml( documents: Vec>>, + context: Option<&Context>, ) -> Result, YamlError> { let mut scenarios = HashMap::new(); @@ -306,6 +324,7 @@ impl YamlParsableHash for Scenario { }, scenario_key.clone(), scenario_yaml, + context, )?; if deployer.is_none() { @@ -630,7 +649,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: scenarios".to_string()) @@ -641,7 +660,7 @@ scenarios: scenario1: test: test "#; - let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("bindings map missing in scenario: scenario1".to_string()) @@ -654,7 +673,7 @@ scenarios: key1: - value1 "#; - let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -669,7 +688,7 @@ scenarios: key1: - value1: value2 "#; - let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -696,7 +715,7 @@ scenarios: bindings: key1: value "#; - let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error.to_string(), YamlError::ParseScenarioConfigSourceError( @@ -731,7 +750,7 @@ scenarios: key2: value deployer: testnet "#; - let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error.to_string(), YamlError::ParseScenarioConfigSourceError( @@ -773,9 +792,11 @@ scenarios: bindings: key4: binding4 "#; - let scenarios = - Scenario::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap(); + let scenarios = Scenario::parse_all_from_yaml( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap(); assert_eq!(scenarios.len(), 4); assert!(scenarios.contains_key("scenario1")); @@ -846,9 +867,11 @@ scenarios: key1: binding2 "#; - let error = - Scenario::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap_err(); + let error = Scenario::parse_all_from_yaml( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap_err(); assert_eq!( error, diff --git a/crates/settings/src/subgraph.rs b/crates/settings/src/subgraph.rs index a07bca47d..a6848b009 100644 --- a/crates/settings/src/subgraph.rs +++ b/crates/settings/src/subgraph.rs @@ -1,4 +1,6 @@ -use crate::yaml::{default_document, require_hash, require_string, YamlError, YamlParsableHash}; +use crate::yaml::{ + context::Context, default_document, require_hash, require_string, YamlError, YamlParsableHash, +}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -28,6 +30,7 @@ impl Subgraph { impl YamlParsableHash for Subgraph { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut subgraphs = HashMap::new(); @@ -96,7 +99,7 @@ mod test { let yaml = r#" test: test "#; - let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: subgraphs".to_string()) @@ -107,7 +110,7 @@ subgraphs: TestSubgraph: test: https://subgraph.com "#; - let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -120,7 +123,7 @@ subgraphs: TestSubgraph: - https://subgraph.com "#; - let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); + let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -132,7 +135,7 @@ subgraphs: subgraphs: TestSubgraph: https://subgraph.com "#; - let result = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap(); + let result = Subgraph::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap(); assert_eq!(result.len(), 1); assert!(result.contains_key("TestSubgraph")); } @@ -149,9 +152,11 @@ subgraphs: subgraph-one: https://api.thegraph.com/subgraphs/name/one subgraph-two: https://api.thegraph.com/subgraphs/name/two "#; - let subgraphs = - Subgraph::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap(); + let subgraphs = Subgraph::parse_all_from_yaml( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap(); assert_eq!(subgraphs.len(), 4); assert_eq!( @@ -183,9 +188,11 @@ subgraphs: subgraphs: mainnet: https://api.thegraph.com/subgraphs/name/mainnet "#; - let error = - Subgraph::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) - .unwrap_err(); + let error = Subgraph::parse_all_from_yaml( + vec![get_document(yaml_one), get_document(yaml_two)], + None, + ) + .unwrap_err(); assert_eq!(error, YamlError::KeyShadowing("mainnet".to_string())); } } diff --git a/crates/settings/src/token.rs b/crates/settings/src/token.rs index b9a180653..19f8314ab 100644 --- a/crates/settings/src/token.rs +++ b/crates/settings/src/token.rs @@ -10,6 +10,7 @@ use std::{collections::HashMap, sync::Arc}; use strict_yaml_rust::StrictYaml; use thiserror::Error; use typeshare::typeshare; +use yaml::context::Context; #[cfg(target_family = "wasm")] use rain_orderbook_bindings::{impl_all_wasm_traits, wasm_traits::prelude::*}; @@ -78,6 +79,7 @@ impl Token { impl YamlParsableHash for Token { fn parse_all_from_yaml( documents: Vec>>, + _: Option<&Context>, ) -> Result, YamlError> { let mut tokens = HashMap::new(); @@ -95,6 +97,7 @@ impl YamlParsableHash for Token { Some("network"), Some(format!("network string missing in token: {token_key}")), )?, + None, ) .map_err(|_| { ParseTokenConfigSourceError::NetworkNotFoundError(token_key.clone()) @@ -291,19 +294,23 @@ mod tests { #[test] fn test_parse_tokens_errors() { - let error = Token::parse_all_from_yaml(vec![get_document( - r#" + let error = Token::parse_all_from_yaml( + vec![get_document( + r#" test: test "#, - )]) + )], + None, + ) .unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: tokens".to_string()) ); - let error = Token::parse_all_from_yaml(vec![get_document( - r#" + let error = Token::parse_all_from_yaml( + vec![get_document( + r#" networks: mainnet: rpc: "https://mainnet.infura.io" @@ -312,15 +319,18 @@ tokens: token1: address: "0x1234567890123456789012345678901234567890" "#, - )]) + )], + None, + ) .unwrap_err(); assert_eq!( error, YamlError::ParseError("network string missing in token: token1".to_string()) ); - let error = Token::parse_all_from_yaml(vec![get_document( - r#" + let error = Token::parse_all_from_yaml( + vec![get_document( + r#" networks: mainnet: rpc: "https://mainnet.infura.io" @@ -330,7 +340,9 @@ tokens: network: "nonexistent" address: "0x1234567890123456789012345678901234567890" "#, - )]) + )], + None, + ) .unwrap_err(); assert_eq!( error, @@ -339,8 +351,9 @@ tokens: ) ); - let error = Token::parse_all_from_yaml(vec![get_document( - r#" + let error = Token::parse_all_from_yaml( + vec![get_document( + r#" networks: mainnet: rpc: "https://mainnet.infura.io" @@ -349,15 +362,18 @@ tokens: token1: network: "mainnet" "#, - )]) + )], + None, + ) .unwrap_err(); assert_eq!( error, YamlError::ParseError("address string missing in token: token1".to_string()) ); - let error = Token::parse_all_from_yaml(vec![get_document( - r#" + let error = Token::parse_all_from_yaml( + vec![get_document( + r#" networks: mainnet: rpc: "https://mainnet.infura.io" @@ -367,11 +383,14 @@ tokens: network: "mainnet" address: "not_a_valid_address" "#, - )]); + )], + None, + ); assert!(error.is_err()); - let error = Token::parse_all_from_yaml(vec![get_document( - r#" + let error = Token::parse_all_from_yaml( + vec![get_document( + r#" networks: mainnet: rpc: "https://mainnet.infura.io" @@ -382,7 +401,9 @@ tokens: address: "0x1234567890123456789012345678901234567890" decimals: "not_a_number" "#, - )]); + )], + None, + ); assert!(error.is_err()); } @@ -415,7 +436,7 @@ tokens: decimals: "6" "#; let tokens = - Token::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + Token::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)], None) .unwrap(); assert_eq!(tokens.len(), 4); @@ -463,7 +484,7 @@ tokens: address: "0x6b175474e89094c44da98b954eedeac495271d0f" "#; let error = - Token::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + Token::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)], None) .unwrap_err(); assert_eq!(error, YamlError::KeyShadowing("dai".to_string())); } diff --git a/crates/settings/src/yaml/context.rs b/crates/settings/src/yaml/context.rs new file mode 100644 index 000000000..fffa3d67a --- /dev/null +++ b/crates/settings/src/yaml/context.rs @@ -0,0 +1,207 @@ +use crate::{Order, OrderIO, Token}; +use std::sync::Arc; +use thiserror::Error; + +#[derive(Debug, Clone, Default)] +pub struct Context { + pub order: Option>, +} + +#[derive(Error, Debug, PartialEq)] +pub enum ContextError { + #[error("No order in context")] + NoOrder, + #[error("Invalid path: {0}")] + InvalidPath(String), + #[error("Invalid index: {0}")] + InvalidIndex(String), + #[error("Property not found: {0}")] + PropertyNotFound(String), +} + +pub trait OrderContext { + fn order(&self) -> Option<&Arc>; + + fn resolve_order_path(&self, parts: &[&str]) -> Result { + let order = self.order().ok_or(ContextError::NoOrder)?; + match parts.first() { + Some(&"inputs") => self.resolve_io_path(&order.inputs, &parts[1..]), + Some(&"outputs") => self.resolve_io_path(&order.outputs, &parts[1..]), + _ => Err(ContextError::InvalidPath(parts.join("."))), + } + } + + fn resolve_io_path(&self, ios: &[OrderIO], parts: &[&str]) -> Result; + fn resolve_token_path(&self, token: &Token, parts: &[&str]) -> Result; +} + +impl OrderContext for Context { + fn order(&self) -> Option<&Arc> { + self.order.as_ref() + } + + fn resolve_io_path(&self, ios: &[OrderIO], parts: &[&str]) -> Result { + let index = parts + .first() + .ok_or_else(|| ContextError::InvalidPath(parts.join(".")))? + .parse::() + .map_err(|_| ContextError::InvalidIndex(parts[0].to_string()))?; + + let io = ios + .get(index) + .ok_or_else(|| ContextError::InvalidIndex(index.to_string()))?; + + match parts.get(1) { + Some(&"token") => self.resolve_token_path(&io.token, &parts[2..]), + Some(&"vault-id") => match &io.vault_id { + Some(vault_id) => Ok(vault_id.to_string()), + None => Err(ContextError::PropertyNotFound("vault-id".to_string())), + }, + _ => Err(ContextError::InvalidPath(parts.join("."))), + } + } + + fn resolve_token_path(&self, token: &Token, parts: &[&str]) -> Result { + match parts.first() { + Some(&"address") => Ok(format!("{:?}", token.address)), + Some(&"symbol") => Ok(token + .symbol + .clone() + .ok_or_else(|| ContextError::PropertyNotFound("symbol".to_string()))?), + Some(&"label") => Ok(token + .label + .clone() + .ok_or_else(|| ContextError::PropertyNotFound("label".to_string()))?), + Some(&"decimals") => Ok(token + .decimals + .ok_or_else(|| ContextError::PropertyNotFound("decimals".to_string()))? + .to_string()), + _ => Err(ContextError::InvalidPath(parts.join("."))), + } + } +} + +impl Context { + pub fn new() -> Self { + Self { order: None } + } + + pub fn with_order(order: Arc) -> Self { + Self { order: Some(order) } + } + + fn resolve_path(&self, path: &str) -> Result { + let parts: Vec<&str> = path.split('.').collect(); + + match parts.first() { + Some(&"order") => self.resolve_order_path(&parts[1..]), + _ => Err(ContextError::InvalidPath(path.to_string())), + } + } + + pub fn interpolate(&self, input: &str) -> Result { + let mut result = input.to_string(); + let mut start = 0; + + while let Some(var_start) = result[start..].find("${") { + let var_start = start + var_start; + if let Some(var_end) = result[var_start..].find('}') { + let var_end = var_start + var_end + 1; + let var = &result[var_start + 2..var_end - 1]; + let replacement = self.resolve_path(var)?; + result.replace_range(var_start..var_end, &replacement); + start = var_start + replacement.len(); + } else { + break; + } + } + + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::*; + use crate::yaml::RwLock; + use crate::Order; + use alloy::primitives::{Address, U256}; + use strict_yaml_rust::StrictYaml; + + fn setup_test_order_with_vault_id() -> Arc { + let token = Token { + document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), + key: "test_token".to_string(), + network: mock_network(), + address: Address::repeat_byte(0x42), + decimals: Some(18), + label: Some("Test Token".to_string()), + symbol: Some("TST".to_string()), + }; + + Arc::new(Order { + document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), + key: "test_order".to_string(), + inputs: vec![OrderIO { + token: Arc::new(token.clone()), + vault_id: Some(U256::from(42)), + }], + outputs: vec![OrderIO { + token: Arc::new(token), + vault_id: None, + }], + network: mock_network(), + deployer: None, + orderbook: None, + }) + } + + #[test] + fn test_context_interpolation() { + let order = setup_test_order_with_vault_id(); + let context = Context::with_order(order.clone()); + + // Test basic interpolation + assert_eq!( + context + .interpolate("Address: ${order.inputs.0.token.address}") + .unwrap(), + "Address: 0x4242424242424242424242424242424242424242" + ); + + // Test multiple interpolations + assert_eq!( + context + .interpolate( + "Symbol: ${order.inputs.0.token.symbol}, \ + Label: ${order.inputs.0.token.label}" + ) + .unwrap(), + "Symbol: TST, Label: Test Token" + ); + + // Test error cases + assert!(context.interpolate("${invalid}").is_err()); + assert!(context + .interpolate("${order.inputs.999.token.address}") + .is_err()); + assert!(context + .interpolate("${order.inputs.0.token.invalid}") + .is_err()); + + // Test vault-id interpolation + assert_eq!( + context + .interpolate("Vault ID: ${order.inputs.0.vault-id}") + .unwrap(), + "Vault ID: 42" + ); + + // Test that missing vault-id returns error + assert!(matches!( + context.interpolate("${order.outputs.0.vault-id}"), + Err(ContextError::PropertyNotFound(_)) + )); + } +} diff --git a/crates/settings/src/yaml/dotrain.rs b/crates/settings/src/yaml/dotrain.rs index 26c5e6956..8507a754a 100644 --- a/crates/settings/src/yaml/dotrain.rs +++ b/crates/settings/src/yaml/dotrain.rs @@ -37,9 +37,9 @@ impl YamlParsable for DotrainYaml { } if validate { - Order::parse_all_from_yaml(documents.clone())?; - Scenario::parse_all_from_yaml(documents.clone())?; - Deployment::parse_all_from_yaml(documents.clone())?; + Order::parse_all_from_yaml(documents.clone(), None)?; + Scenario::parse_all_from_yaml(documents.clone(), None)?; + Deployment::parse_all_from_yaml(documents.clone(), None)?; } Ok(DotrainYaml { documents }) @@ -52,31 +52,31 @@ impl YamlParsable for DotrainYaml { impl DotrainYaml { pub fn get_order_keys(&self) -> Result, YamlError> { - let orders = Order::parse_all_from_yaml(self.documents.clone())?; + let orders = Order::parse_all_from_yaml(self.documents.clone(), None)?; Ok(orders.keys().cloned().collect()) } pub fn get_order(&self, key: &str) -> Result { - Order::parse_from_yaml(self.documents.clone(), key) + Order::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_scenario_keys(&self) -> Result, YamlError> { - let scenarios = Scenario::parse_all_from_yaml(self.documents.clone())?; + let scenarios = Scenario::parse_all_from_yaml(self.documents.clone(), None)?; Ok(scenarios.keys().cloned().collect()) } pub fn get_scenario(&self, key: &str) -> Result { - Scenario::parse_from_yaml(self.documents.clone(), key) + Scenario::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_deployment_keys(&self) -> Result, YamlError> { - let deployments = Deployment::parse_all_from_yaml(self.documents.clone())?; + let deployments = Deployment::parse_all_from_yaml(self.documents.clone(), None)?; Ok(deployments.keys().cloned().collect()) } pub fn get_deployment(&self, key: &str) -> Result { - Deployment::parse_from_yaml(self.documents.clone(), key) + Deployment::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_gui(&self) -> Result, YamlError> { - Gui::parse_from_yaml_optional(self.documents.clone()) + Gui::parse_from_yaml_optional(self.documents.clone(), None) } } @@ -212,6 +212,69 @@ mod tests { - token2 "#; + const HANDLEBARS_YAML: &str = r#" + networks: + mainnet: + rpc: https://mainnet.infura.io + chain-id: 1 + 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: + deployer1: + address: 0x0000000000000000000000000000000000000002 + network: mainnet + orders: + order1: + inputs: + - token: token1 + vault-id: 1 + outputs: + - token: token2 + vault-id: 2 + scenarios: + scenario1: + bindings: + key1: ${order.inputs.0.token.address} + deployer: deployer1 + scenarios: + scenario2: + bindings: + key2: ${order.outputs.0.token.address} + deployments: + deployment1: + order: order1 + scenario: scenario1.scenario2 + gui: + name: Test gui + description: Test description + deployments: + deployment1: + name: Test deployment + description: Test description + deposits: + - token: token1 + presets: + - 100 + - 2000 + fields: + - binding: key1 + name: Binding for ${order.inputs.0.token.label} + description: With token symbol ${order.inputs.0.token.symbol} + presets: + - value: value2 + "#; + #[test] fn test_full_yaml() { let ob_yaml = OrderbookYaml::new(vec![FULL_YAML.to_string()], false).unwrap(); @@ -480,4 +543,27 @@ mod tests { assert_eq!(scenario.bindings.get("key2").unwrap(), "value4"); } } + + #[test] + fn test_handlebars() { + let dotrain_yaml = DotrainYaml::new(vec![HANDLEBARS_YAML.to_string()], false).unwrap(); + + let gui = dotrain_yaml.get_gui().unwrap().unwrap(); + let deployment = gui.deployments.get("deployment1").unwrap(); + + assert_eq!( + deployment.deployment.scenario.bindings.get("key1").unwrap(), + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ); + assert_eq!( + deployment.deployment.scenario.bindings.get("key2").unwrap(), + "0x0000000000000000000000000000000000000002" + ); + + assert_eq!(deployment.fields[0].name, "Binding for Wrapped Ether"); + assert_eq!( + deployment.fields[0].description, + Some("With token symbol WETH".to_string()) + ); + } } diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index f950e598a..5cb5fd39a 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -1,3 +1,4 @@ +pub mod context; pub mod dotrain; pub mod orderbook; @@ -7,6 +8,7 @@ use crate::{ ParseScenarioConfigSourceError, ParseTokenConfigSourceError, }; use alloy::primitives::ruint::ParseError as RuintParseError; +use context::{Context, ContextError}; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use strict_yaml_rust::StrictYamlEmitter; @@ -41,13 +43,15 @@ pub trait YamlParsable: Sized { pub trait YamlParsableHash: Sized + Clone { fn parse_all_from_yaml( documents: Vec>>, + context: Option<&Context>, ) -> Result, YamlError>; fn parse_from_yaml( documents: Vec>>, key: &str, + context: Option<&Context>, ) -> Result { - let all = Self::parse_all_from_yaml(documents)?; + let all = Self::parse_all_from_yaml(documents, context)?; all.get(key) .ok_or_else(|| YamlError::KeyNotFound(key.to_string())) .cloned() @@ -67,10 +71,14 @@ pub trait YamlParsableString { } pub trait YamlParseableValue: Sized { - fn parse_from_yaml(documents: Vec>>) -> Result; + fn parse_from_yaml( + documents: Vec>>, + context: Option<&Context>, + ) -> Result; fn parse_from_yaml_optional( documents: Vec>>, + context: Option<&Context>, ) -> Result, YamlError>; } @@ -116,6 +124,8 @@ pub enum YamlError { ParseScenarioConfigSourceError(#[from] ParseScenarioConfigSourceError), #[error(transparent)] ParseDeploymentConfigSourceError(#[from] ParseDeploymentConfigSourceError), + #[error(transparent)] + ContextError(#[from] ContextError), } impl PartialEq for YamlError { fn eq(&self, other: &Self) -> bool { diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index 60445bafe..5987854cc 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -40,12 +40,12 @@ impl YamlParsable for OrderbookYaml { } if validate { - Network::parse_all_from_yaml(documents.clone())?; - Token::parse_all_from_yaml(documents.clone())?; - Subgraph::parse_all_from_yaml(documents.clone())?; - Orderbook::parse_all_from_yaml(documents.clone())?; - Deployer::parse_all_from_yaml(documents.clone())?; - Metaboard::parse_all_from_yaml(documents.clone())?; + Network::parse_all_from_yaml(documents.clone(), None)?; + Token::parse_all_from_yaml(documents.clone(), None)?; + Subgraph::parse_all_from_yaml(documents.clone(), None)?; + Orderbook::parse_all_from_yaml(documents.clone(), None)?; + Deployer::parse_all_from_yaml(documents.clone(), None)?; + Metaboard::parse_all_from_yaml(documents.clone(), None)?; } Ok(OrderbookYaml { documents }) @@ -58,54 +58,54 @@ impl YamlParsable for OrderbookYaml { impl OrderbookYaml { pub fn get_network_keys(&self) -> Result, YamlError> { - let networks = Network::parse_all_from_yaml(self.documents.clone())?; + let networks = Network::parse_all_from_yaml(self.documents.clone(), None)?; Ok(networks.keys().cloned().collect()) } pub fn get_network(&self, key: &str) -> Result { - Network::parse_from_yaml(self.documents.clone(), key) + Network::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_token_keys(&self) -> Result, YamlError> { - let tokens = Token::parse_all_from_yaml(self.documents.clone())?; + let tokens = Token::parse_all_from_yaml(self.documents.clone(), None)?; Ok(tokens.keys().cloned().collect()) } pub fn get_token(&self, key: &str) -> Result { - Token::parse_from_yaml(self.documents.clone(), key) + Token::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_subgraph_keys(&self) -> Result, YamlError> { - let subgraphs = Subgraph::parse_all_from_yaml(self.documents.clone())?; + let subgraphs = Subgraph::parse_all_from_yaml(self.documents.clone(), None)?; Ok(subgraphs.keys().cloned().collect()) } pub fn get_subgraph(&self, key: &str) -> Result { - Subgraph::parse_from_yaml(self.documents.clone(), key) + Subgraph::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_orderbook_keys(&self) -> Result, YamlError> { - let orderbooks = Orderbook::parse_all_from_yaml(self.documents.clone())?; + let orderbooks = Orderbook::parse_all_from_yaml(self.documents.clone(), None)?; Ok(orderbooks.keys().cloned().collect()) } pub fn get_orderbook(&self, key: &str) -> Result { - Orderbook::parse_from_yaml(self.documents.clone(), key) + Orderbook::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_metaboard_keys(&self) -> Result, YamlError> { - let metaboards = Metaboard::parse_all_from_yaml(self.documents.clone())?; + let metaboards = Metaboard::parse_all_from_yaml(self.documents.clone(), None)?; Ok(metaboards.keys().cloned().collect()) } pub fn get_metaboard(&self, key: &str) -> Result { - Metaboard::parse_from_yaml(self.documents.clone(), key) + Metaboard::parse_from_yaml(self.documents.clone(), key, None) } pub fn add_metaboard(&self, key: &str, value: &str) -> Result<(), YamlError> { Metaboard::add_record_to_yaml(self.documents[0].clone(), key, value) } pub fn get_deployer_keys(&self) -> Result, YamlError> { - let deployers = Deployer::parse_all_from_yaml(self.documents.clone())?; + let deployers = Deployer::parse_all_from_yaml(self.documents.clone(), None)?; Ok(deployers.keys().cloned().collect()) } pub fn get_deployer(&self, key: &str) -> Result { - Deployer::parse_from_yaml(self.documents.clone(), key) + Deployer::parse_from_yaml(self.documents.clone(), key, None) } pub fn get_sentry(&self) -> Result {