diff --git a/crates/common/src/add_order.rs b/crates/common/src/add_order.rs index 36eeaa3fa..32fd28b5e 100644 --- a/crates/common/src/add_order.rs +++ b/crates/common/src/add_order.rs @@ -60,6 +60,10 @@ pub enum AddOrderArgsError { #[cfg(not(target_family = "wasm"))] #[error(transparent)] ForkCallError(#[from] ForkCallError), + #[error("Input token not found for index: {0}")] + InputTokenNotFound(String), + #[error("Output token not found for index: {0}")] + OutputTokenNotFound(String), } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -80,24 +84,29 @@ impl AddOrderArgs { ) -> Result { let random_vault_id: U256 = rand::random(); let mut inputs = vec![]; - for input in &deployment.order.inputs { - if let Some(decimals) = input.token.decimals { + for (i, input) in deployment.order.inputs.iter().enumerate() { + let input_token = input + .token + .as_ref() + .ok_or_else(|| AddOrderArgsError::InputTokenNotFound(i.to_string()))?; + + if let Some(decimals) = input_token.decimals { inputs.push(IO { - token: input.token.address, + token: input_token.address, vaultId: input.vault_id.unwrap_or(random_vault_id), decimals, }); } else { - let client = ReadableClientHttp::new_from_url(input.token.network.rpc.to_string())?; + let client = ReadableClientHttp::new_from_url(input_token.network.rpc.to_string())?; let parameters = ReadContractParameters { - address: input.token.address, + address: input_token.address, call: decimalsCall {}, block_number: None, gas: None, }; let decimals = client.read(parameters).await?._0; inputs.push(IO { - token: input.token.address, + token: input_token.address, vaultId: input.vault_id.unwrap_or(random_vault_id), decimals, }); @@ -105,25 +114,30 @@ impl AddOrderArgs { } let mut outputs = vec![]; - for output in &deployment.order.outputs { - if let Some(decimals) = output.token.decimals { + for (i, output) in deployment.order.outputs.iter().enumerate() { + let output_token = output + .token + .as_ref() + .ok_or_else(|| AddOrderArgsError::OutputTokenNotFound(i.to_string()))?; + + if let Some(decimals) = output_token.decimals { outputs.push(IO { - token: output.token.address, + token: output_token.address, vaultId: output.vault_id.unwrap_or(random_vault_id), decimals, }); } else { let client = - ReadableClientHttp::new_from_url(output.token.network.rpc.to_string())?; + ReadableClientHttp::new_from_url(output_token.network.rpc.to_string())?; let parameters = ReadContractParameters { - address: output.token.address, + address: output_token.address, call: decimalsCall {}, block_number: None, gas: None, }; let decimals = client.read(parameters).await?._0; outputs.push(IO { - token: output.token.address, + token: output_token.address, vaultId: output.vault_id.unwrap_or(random_vault_id), decimals, }); @@ -489,16 +503,16 @@ price: 2e18; key: "".to_string(), inputs: vec![ OrderIO { - token: token1_arc.clone(), + token: Some(token1_arc.clone()), vault_id: None, }, OrderIO { - token: token2_arc.clone(), + token: Some(token2_arc.clone()), vault_id: Some(known_vault_id), }, ], outputs: vec![OrderIO { - token: token3_arc.clone(), + token: Some(token3_arc.clone()), vault_id: None, }], network: network_arc.clone(), @@ -599,16 +613,16 @@ _ _: 0 0; key: "".to_string(), inputs: vec![ OrderIO { - token: token1_arc.clone(), + token: Some(token1_arc.clone()), vault_id: Some(U256::from(2)), }, OrderIO { - token: token2_arc.clone(), + token: Some(token2_arc.clone()), vault_id: Some(U256::from(1)), }, ], outputs: vec![OrderIO { - token: token3_arc.clone(), + token: Some(token3_arc.clone()), vault_id: Some(U256::from(4)), }], network: network_arc.clone(), @@ -745,16 +759,16 @@ _ _: 0 0; key: "".to_string(), inputs: vec![ OrderIO { - token: token1_arc.clone(), + token: Some(token1_arc.clone()), vault_id: None, }, OrderIO { - token: token2_arc.clone(), + token: Some(token2_arc.clone()), vault_id: Some(known_vault_id), }, ], outputs: vec![OrderIO { - token: token3_arc.clone(), + token: Some(token3_arc.clone()), vault_id: None, }], network: network_arc.clone(), diff --git a/crates/common/src/dotrain_order/calldata.rs b/crates/common/src/dotrain_order/calldata.rs index 19d755178..88d4a3504 100644 --- a/crates/common/src/dotrain_order/calldata.rs +++ b/crates/common/src/dotrain_order/calldata.rs @@ -45,18 +45,23 @@ impl DotrainOrder { &self, deployment_name: &str, owner: &str, - token_deposits: &HashMap, + token_deposits: &HashMap, ) -> Result, DotrainOrderCalldataError> { let deployment = self.get_deployment(deployment_name)?; let orderbook = self.get_orderbook(deployment_name)?; let mut calldatas = Vec::new(); - for output in &deployment.order.outputs { - if let Some(deposit_amount) = token_deposits.get(&output.token.address) { + for (i, output) in deployment.order.outputs.iter().enumerate() { + let output_token = output + .token + .as_ref() + .ok_or_else(|| DotrainOrderCalldataError::OutputTokenNotFound(i.to_string()))?; + + if let Some(deposit_amount) = token_deposits.get(&output_token.key) { let deposit_amount = deposit_amount.to_owned(); let deposit_args = DepositArgs { - token: output.token.address, + token: output_token.address, amount: deposit_amount, vault_id: U256::default(), }; @@ -75,7 +80,7 @@ impl DotrainOrder { .get_approve_calldata(transaction_args, allowance) .await?; calldatas.push(ApprovalCalldata { - token: output.token.address, + token: output_token.address, calldata: Bytes::copy_from_slice(&approve_call), }); } @@ -94,14 +99,18 @@ impl DotrainOrder { let mut calldatas = Vec::new(); for (i, output) in deployment.order.outputs.iter().enumerate() { + let output_token = output + .token + .as_ref() + .ok_or_else(|| DotrainOrderCalldataError::OutputTokenNotFound(i.to_string()))?; let vault_id = output .vault_id .ok_or(DotrainOrderCalldataError::VaultIdNotFound(i.to_string()))?; let token_deposit = token_deposits - .get(&(vault_id, output.token.address)) + .get(&(vault_id, output_token.address)) .ok_or(DotrainOrderCalldataError::TokenNotFound( - output.token.address.to_string(), + output_token.address.to_string(), ))?; if *token_deposit == U256::ZERO { @@ -109,7 +118,7 @@ impl DotrainOrder { } let calldata = DepositArgs { - token: output.token.address, + token: output_token.address, amount: token_deposit.to_owned(), vault_id, } @@ -150,12 +159,15 @@ pub enum DotrainOrderCalldataError { #[error("Orderbook not found")] OrderbookNotFound, - #[error("Token not found {0}")] - TokenNotFound(String), + #[error("Token not found for output index: {0}")] + OutputTokenNotFound(String), #[error("Vault id not found for output index: {0}")] VaultIdNotFound(String), + #[error("Token not found {0}")] + TokenNotFound(String), + #[error(transparent)] DepositError(#[from] DepositError), diff --git a/crates/js_api/src/gui/deposits.rs b/crates/js_api/src/gui/deposits.rs index fad1e7b8b..d2874d9f1 100644 --- a/crates/js_api/src/gui/deposits.rs +++ b/crates/js_api/src/gui/deposits.rs @@ -16,12 +16,12 @@ impl DotrainOrderGui { let deployment = self.get_current_deployment()?; self.deposits .iter() - .map(|(token, value)| { + .map(|(key, value)| { let gui_deposit = deployment .deposits .iter() - .find(|dg| dg.token.key == *token) - .ok_or(GuiError::DepositTokenNotFound(token.clone()))?; + .find(|dg| dg.token.as_ref().map_or(false, |t| t.key == *key)) + .ok_or(GuiError::DepositTokenNotFound(key.clone()))?; let amount: String = if value.is_preset { let index = value .value @@ -35,10 +35,16 @@ impl DotrainOrderGui { } else { value.value.clone() }; + + if gui_deposit.token.is_none() { + return Err(GuiError::TokenMustBeSelected(key.clone())); + } + let token = gui_deposit.token.as_ref().unwrap(); + Ok(TokenDeposit { - token: gui_deposit.token.key.clone(), + token: token.key.clone(), amount, - address: gui_deposit.token.address, + address: token.address, }) }) .collect::, GuiError>>() @@ -50,7 +56,7 @@ impl DotrainOrderGui { let gui_deposit = deployment .deposits .iter() - .find(|dg| dg.token.key == token) + .find(|dg| dg.token.as_ref().map_or(false, |t| t.key == token)) .ok_or(GuiError::DepositTokenNotFound(token.clone()))?; let value = if let Some(index) = gui_deposit.presets.iter().position(|p| **p == amount) { @@ -75,13 +81,13 @@ impl DotrainOrderGui { } #[wasm_bindgen(js_name = "getDepositPresets")] - pub fn get_deposit_presets(&self, token: String) -> Result, GuiError> { + pub fn get_deposit_presets(&self, key: String) -> Result, GuiError> { let deployment = self.get_current_deployment()?; let gui_deposit = deployment .deposits .iter() - .find(|dg| dg.token.key == token) - .ok_or(GuiError::DepositTokenNotFound(token.clone()))?; + .find(|dg| dg.token.as_ref().map_or(false, |t| t.key == key)) + .ok_or(GuiError::DepositTokenNotFound(key.clone()))?; Ok(gui_deposit.presets.clone()) } } diff --git a/crates/js_api/src/gui/mod.rs b/crates/js_api/src/gui/mod.rs index 35701359a..67b6e7889 100644 --- a/crates/js_api/src/gui/mod.rs +++ b/crates/js_api/src/gui/mod.rs @@ -9,7 +9,7 @@ use rain_orderbook_app_settings::{ use rain_orderbook_bindings::{impl_all_wasm_traits, wasm_traits::prelude::*}; use rain_orderbook_common::{ dotrain_order::{calldata::DotrainOrderCalldataError, DotrainOrder, DotrainOrderError}, - erc20::{TokenInfo, ERC20}, + erc20::ERC20, }; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -27,8 +27,13 @@ pub struct AvailableDeployments(Vec); impl_all_wasm_traits!(AvailableDeployments); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct TokenInfos(#[tsify(type = "Map")] BTreeMap); -impl_all_wasm_traits!(TokenInfos); +pub struct TokenInfo { + pub address: Address, + pub decimals: u8, + pub name: String, + pub symbol: String, +} +impl_all_wasm_traits!(TokenInfo); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] pub struct GuiDetails { @@ -44,8 +49,6 @@ pub struct DotrainOrderGui { selected_deployment: String, field_values: BTreeMap, deposits: BTreeMap, - select_tokens: Option>, - onchain_token_info: BTreeMap, } #[wasm_bindgen] impl DotrainOrderGui { @@ -67,7 +70,6 @@ impl DotrainOrderGui { pub async fn choose_deployment( dotrain: String, deployment_name: String, - multicall_address: Option, ) -> Result { let dotrain_order = DotrainOrder::new(dotrain, None).await?; @@ -75,44 +77,8 @@ impl DotrainOrderGui { .dotrain_yaml() .get_gui()? .ok_or(GuiError::GuiConfigNotFound)?; - - let (_, gui_deployment) = gui - .deployments - .into_iter() - .find(|(name, _)| name == &deployment_name) - .ok_or(GuiError::DeploymentNotFound(deployment_name.clone()))?; - - let select_tokens = gui_deployment.select_tokens.clone().map(|tokens| { - tokens - .iter() - .map(|token: &String| (token.clone(), Address::ZERO)) - .collect::>() - }); - - let rpc_url = gui_deployment - .deployment - .order - .orderbook - .clone() - .ok_or(GuiError::OrderbookNotFound)? - .network - .rpc - .clone(); - let mut onchain_token_info: BTreeMap = BTreeMap::new(); - for token in gui_deployment.deposits.iter() { - if onchain_token_info.contains_key(&token.token.address) { - continue; - } - - if let Some(select_tokens) = &select_tokens { - if select_tokens.contains_key(&token.token.key) { - continue; - } - } - - let erc20 = ERC20::new(rpc_url.clone(), token.token.address); - let token_info = erc20.token_info(multicall_address.clone()).await?; - onchain_token_info.insert(token.token.address, token_info); + if !gui.deployments.contains_key(&deployment_name) { + return Err(GuiError::DeploymentNotFound(deployment_name.clone())); } Ok(Self { @@ -120,8 +86,6 @@ impl DotrainOrderGui { selected_deployment: deployment_name.clone(), field_values: BTreeMap::new(), deposits: BTreeMap::new(), - select_tokens, - onchain_token_info, }) } @@ -148,12 +112,44 @@ impl DotrainOrderGui { Ok(gui_deployment.clone()) } - /// Get all token infos in input and output vaults + /// Get token info for a given key /// - /// Returns a map of token address to [`TokenInfo`] - #[wasm_bindgen(js_name = "getTokenInfos")] - pub fn get_token_infos(&self) -> Result { - Ok(TokenInfos(self.onchain_token_info.clone())) + /// Returns a [`TokenInfo`] + #[wasm_bindgen(js_name = "getTokenInfo")] + pub async fn get_token_info(&self, key: String) -> Result { + let deployment = self.get_current_deployment()?; + let token = self.dotrain_order.orderbook_yaml().get_token(&key)?; + + let token_info = + if token.decimals.is_some() && token.label.is_some() && token.symbol.is_some() { + TokenInfo { + address: token.address, + decimals: token.decimals.unwrap(), + name: token.label.unwrap(), + symbol: token.symbol.unwrap(), + } + } else { + let rpc_url = deployment + .deployment + .order + .orderbook + .clone() + .ok_or(GuiError::OrderbookNotFound)? + .network + .rpc + .clone(); + let erc20 = ERC20::new(rpc_url, token.address); + let onchain_info = erc20.token_info(None).await?; + + TokenInfo { + address: token.address, + decimals: token.decimals.unwrap_or(onchain_info.decimals), + name: token.label.unwrap_or(onchain_info.name), + symbol: token.symbol.unwrap_or(onchain_info.symbol), + } + }; + + Ok(token_info) } #[wasm_bindgen(js_name = "getGuiDetails")] diff --git a/crates/js_api/src/gui/order_operations.rs b/crates/js_api/src/gui/order_operations.rs index e917edc5c..6390962b3 100644 --- a/crates/js_api/src/gui/order_operations.rs +++ b/crates/js_api/src/gui/order_operations.rs @@ -54,39 +54,42 @@ impl DotrainOrderGui { .cloned() } - fn get_deposits_as_map(&self) -> Result, GuiError> { - let mut map: HashMap = HashMap::new(); + async fn get_deposits_as_map(&self) -> Result, GuiError> { + let mut map: HashMap = HashMap::new(); for d in self.get_deposits()? { - let token_decimals = self - .onchain_token_info - .get(&d.address) - .ok_or(GuiError::TokenNotFound(d.address.to_string()))? - .decimals; - let amount = parse_units(&d.amount, token_decimals)?.into(); - map.insert(d.address, amount); + let token_info = self.get_token_info(d.token.clone()).await?; + let amount = parse_units(&d.amount, token_info.decimals)?.into(); + map.insert(d.token, amount); } Ok(map) } - fn get_vaults_and_deposits( + async fn get_vaults_and_deposits( &self, deployment: &GuiDeployment, ) -> Result, GuiError> { - let deposits_map = self.get_deposits_as_map()?; + let deposits_map = self.get_deposits_as_map().await?; let results = deployment .deployment .order .outputs .clone() .into_iter() - .filter(|output| deposits_map.contains_key(&output.token.address)) + .filter(|output| { + output + .token + .as_ref() + .map_or(false, |token| deposits_map.contains_key(&token.key)) + }) .map(|output| { - ( - output.clone(), - *deposits_map.get(&output.token.address).unwrap(), - ) + if output.token.is_none() { + return Err(GuiError::SelectTokensNotSet); + } + let token = output.token.as_ref().unwrap(); + + Ok((output.clone(), *deposits_map.get(&token.key).unwrap())) }) - .collect(); + .collect::, GuiError>>()?; Ok(results) } @@ -118,15 +121,20 @@ impl DotrainOrderGui { #[wasm_bindgen(js_name = "checkAllowances")] pub async fn check_allowances(&self, owner: String) -> Result { let deployment = self.get_current_deployment()?; - self.check_token_addresses()?; + self.check_select_tokens()?; let orderbook = self.get_orderbook()?; - let vaults_and_deposits = self.get_vaults_and_deposits(&deployment)?; + let vaults_and_deposits = self.get_vaults_and_deposits(&deployment).await?; let mut results = Vec::new(); for (order_io, amount) in vaults_and_deposits.iter() { + if order_io.token.is_none() { + return Err(GuiError::SelectTokensNotSet); + } + let token = order_io.token.as_ref().unwrap(); + let deposit_args = DepositArgs { - token: order_io.token.address, + token: token.address, vault_id: rand::random(), amount: *amount, }; @@ -148,11 +156,15 @@ impl DotrainOrderGui { owner: String, ) -> Result { let deployment = self.get_current_deployment()?; - self.check_token_addresses()?; + self.check_select_tokens()?; let calldatas = self .dotrain_order - .generate_approval_calldatas(&deployment.key, &owner, &self.get_deposits_as_map()?) + .generate_approval_calldatas( + &deployment.key, + &owner, + &self.get_deposits_as_map().await?, + ) .await?; Ok(ApprovalCalldataResult(calldatas)) } @@ -187,19 +199,26 @@ impl DotrainOrderGui { #[wasm_bindgen(js_name = "generateDepositCalldatas")] pub async fn generate_deposit_calldatas(&mut self) -> Result { let deployment = self.get_current_deployment()?; - self.check_token_addresses()?; + self.check_select_tokens()?; self.populate_vault_ids(&deployment)?; let deployment = self.get_current_deployment()?; let token_deposits = self - .get_vaults_and_deposits(&deployment)? + .get_vaults_and_deposits(&deployment) + .await? .iter() .enumerate() .map(|(i, (order_io, amount))| { let vault_id = order_io .vault_id .ok_or(GuiError::VaultIdNotFound(i.to_string()))?; - Ok(((vault_id, order_io.token.address), *amount)) + + if order_io.token.is_none() { + return Err(GuiError::SelectTokensNotSet); + } + let token = order_io.token.as_ref().unwrap(); + + Ok(((vault_id, token.address), *amount)) }) .collect::, GuiError>>()?; let calldatas = self @@ -215,7 +234,7 @@ impl DotrainOrderGui { &mut self, ) -> Result { let deployment = self.get_current_deployment()?; - self.check_token_addresses()?; + self.check_select_tokens()?; self.populate_vault_ids(&deployment)?; self.update_config_source_bindings(&deployment)?; let deployment = self.get_current_deployment()?; @@ -232,20 +251,27 @@ impl DotrainOrderGui { &mut self, ) -> Result { let deployment = self.get_current_deployment()?; - self.check_token_addresses()?; + self.check_select_tokens()?; self.populate_vault_ids(&deployment)?; self.update_config_source_bindings(&deployment)?; let deployment = self.get_current_deployment()?; let token_deposits = self - .get_vaults_and_deposits(&deployment)? + .get_vaults_and_deposits(&deployment) + .await? .iter() .enumerate() .map(|(i, (order_io, amount))| { let vault_id = order_io .vault_id .ok_or(GuiError::VaultIdNotFound(i.to_string()))?; - Ok(((vault_id, order_io.token.address), *amount)) + + if order_io.token.is_none() { + return Err(GuiError::SelectTokensNotSet); + } + let token = order_io.token.as_ref().unwrap(); + + Ok(((vault_id, token.address), *amount)) }) .collect::, GuiError>>()?; diff --git a/crates/js_api/src/gui/select_tokens.rs b/crates/js_api/src/gui/select_tokens.rs index 005662b5d..20ae160c4 100644 --- a/crates/js_api/src/gui/select_tokens.rs +++ b/crates/js_api/src/gui/select_tokens.rs @@ -1,54 +1,51 @@ -use std::str::FromStr; - use super::*; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct SelectTokens(#[tsify(type = "Map")] BTreeMap); -impl_all_wasm_traits!(SelectTokens); +use rain_orderbook_app_settings::token::Token; +use std::str::FromStr; #[wasm_bindgen] impl DotrainOrderGui { - pub fn check_token_addresses(&self) -> Result<(), GuiError> { - if let Some(select_tokens) = &self.select_tokens { - for (token, address) in select_tokens.iter() { - if address == &Address::ZERO { - return Err(GuiError::TokenMustBeSelected(token.clone())); + #[wasm_bindgen(js_name = "getSelectTokens")] + pub fn get_select_tokens(&self) -> Result, GuiError> { + let deployment = self.get_current_deployment()?; + Ok(deployment.select_tokens.unwrap_or(vec![])) + } + + #[wasm_bindgen(js_name = "isSelectTokenSet")] + pub fn is_select_token_set(&self, key: String) -> Result { + Ok(self.dotrain_order.orderbook_yaml().get_token(&key).is_ok()) + } + + #[wasm_bindgen(js_name = "checkSelectTokens")] + pub fn check_select_tokens(&self) -> Result<(), GuiError> { + let deployment = self.get_current_deployment()?; + + if let Some(select_tokens) = deployment.select_tokens { + for key in select_tokens { + if self.dotrain_order.orderbook_yaml().get_token(&key).is_err() { + return Err(GuiError::TokenMustBeSelected(key.clone())); } } } - Ok(()) - } - /// Get all selected tokens and their addresses - /// - /// Returns a map of token name to address - #[wasm_bindgen(js_name = "getSelectTokens")] - pub fn get_select_tokens(&self) -> Result { - let select_tokens = self - .select_tokens - .clone() - .ok_or(GuiError::SelectTokensNotSet)?; - Ok(SelectTokens(select_tokens)) + Ok(()) } - #[wasm_bindgen(js_name = "saveSelectTokenAddress")] - pub async fn save_select_token_address( + #[wasm_bindgen(js_name = "saveSelectToken")] + pub async fn save_select_token( &mut self, - token_name: String, + key: String, address: String, ) -> Result<(), GuiError> { let deployment = self.get_current_deployment()?; - let mut select_tokens = self - .select_tokens - .clone() - .ok_or(GuiError::SelectTokensNotSet)?; - if !select_tokens.contains_key(&token_name) { - return Err(GuiError::TokenNotFound(token_name.clone())); + if deployment.select_tokens.is_none() { + return Err(GuiError::SelectTokensNotSet); + } + let select_tokens = deployment.select_tokens.unwrap(); + if !select_tokens.contains(&key) { + return Err(GuiError::TokenNotFound(key.clone())); } let address = Address::from_str(&address)?; - select_tokens.insert(token_name.clone(), address); - self.select_tokens = Some(select_tokens); let rpc_url = deployment .deployment @@ -61,12 +58,31 @@ impl DotrainOrderGui { .clone(); let erc20 = ERC20::new(rpc_url.clone(), address); let token_info = erc20.token_info(None).await?; - self.onchain_token_info.insert(address, token_info); - self.dotrain_order - .orderbook_yaml() - .get_token(&token_name)? - .update_address(&address.to_string())?; + Token::add_record_to_yaml( + self.dotrain_order.orderbook_yaml().documents, + &key, + &deployment.deployment.scenario.deployer.network.key, + &address.to_string(), + Some(&token_info.decimals.to_string()), + Some(&token_info.name), + Some(&token_info.symbol), + )?; + Ok(()) + } + + #[wasm_bindgen(js_name = "removeSelectToken")] + pub fn remove_select_token(&mut self, key: String) -> Result<(), GuiError> { + let deployment = self.get_current_deployment()?; + if deployment.select_tokens.is_none() { + return Err(GuiError::SelectTokensNotSet); + } + let select_tokens = deployment.select_tokens.unwrap(); + if !select_tokens.contains(&key) { + return Err(GuiError::TokenNotFound(key.clone())); + } + + Token::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key)?; Ok(()) } } diff --git a/crates/settings/src/deployment.rs b/crates/settings/src/deployment.rs index 8053382cf..e46c65cd8 100644 --- a/crates/settings/src/deployment.rs +++ b/crates/settings/src/deployment.rs @@ -33,7 +33,7 @@ impl_all_wasm_traits!(Deployment); impl YamlParsableHash for Deployment { fn parse_all_from_yaml( documents: Vec>>, - _: Option<&Context>, + context: Option<&Context>, ) -> Result, YamlError> { let mut deployments = HashMap::new(); @@ -53,10 +53,11 @@ impl YamlParsableHash for Deployment { "order string missing in deployment: {deployment_key}" )), )?, - None, + context, )?; - let context = Context::with_order(Arc::new(order.clone())); + let mut context = Context::new(); + context.add_order(Arc::new(order.clone())); let scenario = Scenario::parse_from_yaml( documents.clone(), diff --git a/crates/settings/src/gui.rs b/crates/settings/src/gui.rs index 5b50b849a..6f97d15e6 100644 --- a/crates/settings/src/gui.rs +++ b/crates/settings/src/gui.rs @@ -98,7 +98,7 @@ impl GuiConfigSource { .map(Arc::clone)?; Ok(GuiDeposit { - token: token.clone(), + token: Some(token.clone()), presets: deposit_source.presets.clone(), }) }) @@ -187,8 +187,8 @@ impl_all_wasm_traits!(GuiPreset); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] pub struct GuiDeposit { - #[typeshare(typescript(type = "Token"))] - pub token: Arc, + #[typeshare(typescript(type = "Token | undefined"))] + pub token: Option>, #[cfg_attr(target_family = "wasm", tsify(type = "string[]"))] pub presets: Vec, } @@ -306,10 +306,32 @@ impl YamlParseableValue for Gui { for (deployment_name, deployment_yaml) in deployments { let deployment_name = deployment_name.as_str().unwrap_or_default().to_string(); - let deployment = - Deployment::parse_from_yaml(documents.clone(), &deployment_name, None)?; + let mut context = Context::new(); - let context = Context::with_order(deployment.order.clone()); + let select_tokens = match optional_vec(deployment_yaml, "select-tokens") { + Some(tokens) => Some( + tokens + .iter() + .enumerate() + .map(|(select_token_index, select_token_value)| { + Ok(select_token_value.as_str().ok_or(YamlError::ParseError(format!( + "select-token value must be a string for select-token index: {select_token_index} in gui deployment: {deployment_name}", + )))?.to_string()) + }) + .collect::, YamlError>>()?, + ), + None => None, + }; + if let Some(ref select_tokens) = select_tokens { + context.add_select_tokens(select_tokens.clone()); + } + + let deployment = Deployment::parse_from_yaml( + documents.clone(), + &deployment_name, + Some(&context), + )?; + context.add_order(deployment.order.clone()); let name = require_string( deployment_yaml, @@ -340,7 +362,7 @@ impl YamlParseableValue for Gui { Some(format!( "token string missing for deposit index: {deposit_index} in gui deployment: {deployment_name}", )), - )?, None)?; + )?, None); let presets = require_vec( deposit_value, @@ -359,7 +381,7 @@ impl YamlParseableValue for Gui { .collect::, YamlError>>()?; let gui_deposit = GuiDeposit { - token: Arc::new(token), + token: token.ok().map(Arc::new), presets, }; Ok(gui_deposit) @@ -425,21 +447,6 @@ impl YamlParseableValue for Gui { }) .collect::, YamlError>>()?; - let select_tokens = match optional_vec(deployment_yaml, "select-tokens") { - Some(tokens) => Some( - tokens - .iter() - .enumerate() - .map(|(select_token_index, select_token_value)| { - Ok(select_token_value.as_str().ok_or(YamlError::ParseError(format!( - "select-token value must be a string for select-token index: {select_token_index} in gui deployment: {deployment_name}", - )))?.to_string()) - }) - .collect::, YamlError>>()?, - ), - None => None, - }; - let gui_deployment = GuiDeployment { document: document.clone(), key: deployment_name.clone(), @@ -585,7 +592,10 @@ mod tests { assert_eq!(deployment.description, "test-deployment-description"); assert_eq!(deployment.deposits.len(), 1); let deposit = &deployment.deposits[0]; - assert_eq!(deposit.token.label, Some("test-token".to_string())); + assert_eq!( + deposit.token.as_ref().unwrap().label, + Some("test-token".to_string()) + ); assert_eq!(deposit.presets.len(), 2); assert_eq!(deposit.presets[0], "1.3".to_string()); assert_eq!(deposit.presets[1], "2.7".to_string()); @@ -855,24 +865,6 @@ gui: let yaml = r#" gui: - name: test - description: test - deployments: - deployment1: - name: test - description: test - deposits: - - token: test -"#; - 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#" -gui: name: test description: test deployments: @@ -1149,12 +1141,12 @@ gui: let deployment = gui.deployments.get("deployment1").unwrap(); assert_eq!(deployment.name, "test"); assert_eq!(deployment.description, "test"); - assert_eq!(deployment.deposits[0].token.key, "token1"); + assert_eq!(deployment.deposits[0].token.as_ref().unwrap().key, "token1"); let deployment = gui.deployments.get("deployment2").unwrap(); assert_eq!(deployment.name, "test another"); assert_eq!(deployment.description, "test another"); - assert_eq!(deployment.deposits[0].token.key, "token2"); + assert_eq!(deployment.deposits[0].token.as_ref().unwrap().key, "token2"); } #[test] diff --git a/crates/settings/src/order.rs b/crates/settings/src/order.rs index d87918b19..f7f5ef6c2 100644 --- a/crates/settings/src/order.rs +++ b/crates/settings/src/order.rs @@ -10,8 +10,9 @@ use strict_yaml_rust::StrictYaml; use thiserror::Error; use typeshare::typeshare; use yaml::{ - context::Context, default_document, optional_string, require_hash, require_string, require_vec, - YamlError, YamlParsableHash, + context::{Context, SelectTokensContext}, + default_document, optional_string, require_hash, require_string, require_vec, YamlError, + YamlParsableHash, }; #[cfg(target_family = "wasm")] @@ -22,8 +23,8 @@ use rain_orderbook_bindings::{impl_all_wasm_traits, wasm_traits::prelude::*}; #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "kebab-case")] pub struct OrderIO { - #[typeshare(typescript(type = "Token"))] - pub token: Arc, + #[typeshare(typescript(type = "Token | undefined"))] + pub token: Option>, #[typeshare(typescript(type = "string"))] #[cfg_attr( target_family = "wasm", @@ -44,10 +45,10 @@ pub struct Order { pub document: Arc>, pub key: String, #[typeshare(typescript(type = "OrderIO[]"))] - #[cfg_attr(target_family = "wasm", tsify(type = "Vault[]"))] + #[cfg_attr(target_family = "wasm", tsify(type = "OrderIO[]"))] pub inputs: Vec, #[typeshare(typescript(type = "OrderIO[]"))] - #[cfg_attr(target_family = "wasm", tsify(type = "Vault[]"))] + #[cfg_attr(target_family = "wasm", tsify(type = "OrderIO[]"))] pub outputs: Vec, #[typeshare(typescript(type = "Network"))] pub network: Arc, @@ -227,7 +228,7 @@ impl Order { impl YamlParsableHash for Order { fn parse_all_from_yaml( documents: Vec>>, - _: Option<&Context>, + context: Option<&Context>, ) -> Result, YamlError> { let mut orders = HashMap::new(); @@ -297,16 +298,24 @@ 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, None)?; + let token = Token::parse_from_yaml(documents.clone(), &token_name, None); - if let Some(n) = &network { - if token.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::NetworkNotMatch, - )); + if let Ok(ref token) = token { + if let Some(n) = &network { + if token.network != *n { + return Err(YamlError::ParseOrderConfigSourceError( + ParseOrderConfigSourceError::NetworkNotMatch, + )); + } + } else { + network = Some(token.network.clone()); + } + } else if let Some(context) = context { + if !context.is_select_token(&token_name) { + return Err(YamlError::ParseError(format!( + "yaml data for token: {token_name} not found in input index: {i} in order: {order_key}" + ))); } - } else { - network = Some(token.network.clone()); } let vault_id = match optional_string(input, "vault-id") { @@ -315,7 +324,7 @@ impl YamlParsableHash for Order { }; Ok(OrderIO { - token: Arc::new(token), + token: token.ok().map(Arc::new), vault_id, }) }) @@ -336,16 +345,24 @@ 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, None)?; + let token = Token::parse_from_yaml(documents.clone(), &token_name, None); - if let Some(n) = &network { - if token.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::NetworkNotMatch, - )); + if let Ok(ref token) = token { + if let Some(n) = &network { + if token.network != *n { + return Err(YamlError::ParseOrderConfigSourceError( + ParseOrderConfigSourceError::NetworkNotMatch, + )); + } + } else { + network = Some(token.network.clone()); + } + } else if let Some(context) = context { + if !context.is_select_token(&token_name) { + return Err(YamlError::ParseError(format!( + "yaml data for token: {token_name} not found in output index: {i} in order: {order_key}" + ))); } - } else { - network = Some(token.network.clone()); } let vault_id = match optional_string(output, "vault-id") { @@ -354,7 +371,7 @@ impl YamlParsableHash for Order { }; Ok(OrderIO { - token: Arc::new(token), + token: token.ok().map(Arc::new), vault_id, }) }) @@ -499,7 +516,7 @@ impl OrderConfigSource { if let Some(n) = &network { if v.network == *n { Ok(OrderIO { - token: v.clone(), + token: Some(v.clone()), vault_id: input.vault_id, }) } else { @@ -508,7 +525,7 @@ impl OrderConfigSource { } else { network = Some(v.network.clone()); Ok(OrderIO { - token: v.clone(), + token: Some(v.clone()), vault_id: input.vault_id, }) } @@ -529,7 +546,7 @@ impl OrderConfigSource { if let Some(n) = &network { if v.network == *n { Ok(OrderIO { - token: v.clone(), + token: Some(v.clone()), vault_id: output.vault_id, }) } else { @@ -538,7 +555,7 @@ impl OrderConfigSource { } else { network = Some(v.network.clone()); Ok(OrderIO { - token: v.clone(), + token: Some(v.clone()), vault_id: output.vault_id, }) } @@ -611,7 +628,7 @@ mod tests { order .inputs .iter() - .map(|v| v.token.clone()) + .map(|v| v.token.clone().unwrap()) .collect::>(), vec![token_input] ); @@ -619,7 +636,7 @@ mod tests { order .outputs .iter() - .map(|v| v.token.clone()) + .map(|v| v.token.clone().unwrap()) .collect::>(), vec![token_output] ); @@ -733,18 +750,6 @@ orders: ) ); - let yaml = r#" -orders: - order1: - inputs: - - token: eth -"#; - let error = Order::parse_all_from_yaml(vec![get_document(yaml)], None).unwrap_err(); - assert_eq!( - error, - YamlError::ParseError("missing field: tokens".to_string()) - ); - let yaml = r#" networks: mainnet: @@ -797,6 +802,10 @@ networks: mainnet: rpc: "https://mainnet.infura.io" chain-id: "1" +deployers: + mainnet: + address: 0x0000000000000000000000000000000000000001 + network: mainnet tokens: token-one: network: mainnet @@ -806,6 +815,7 @@ tokens: address: 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa orders: OrderOne: + deployer: mainnet inputs: - token: token-one outputs: @@ -814,6 +824,7 @@ orders: let yaml_two = r#" orders: OrderTwo: + deployer: mainnet inputs: - token: token-one outputs: @@ -838,6 +849,10 @@ networks: mainnet: rpc: "https://mainnet.infura.io" chain-id: "1" +deployers: + mainnet: + address: 0x0000000000000000000000000000000000000001 + network: mainnet tokens: token-one: network: mainnet @@ -847,6 +862,7 @@ tokens: address: 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa orders: DuplicateOrder: + deployer: mainnet inputs: - token: token-one outputs: @@ -855,6 +871,7 @@ orders: let yaml_two = r#" orders: DuplicateOrder: + deployer: mainnet inputs: - token: token-one outputs: diff --git a/crates/settings/src/token.rs b/crates/settings/src/token.rs index 19f8314ab..ab29bfd86 100644 --- a/crates/settings/src/token.rs +++ b/crates/settings/src/token.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::RwLock; use std::{collections::HashMap, sync::Arc}; +use strict_yaml_rust::strict_yaml::Hash; use strict_yaml_rust::StrictYaml; use thiserror::Error; use typeshare::typeshare; @@ -75,6 +76,110 @@ impl Token { Ok(self.clone()) } + + pub fn add_record_to_yaml( + documents: Vec>>, + key: &str, + network_key: &str, + address: &str, + decimals: Option<&str>, + label: Option<&str>, + symbol: Option<&str>, + ) -> Result<(), YamlError> { + if Token::parse_from_yaml(documents.clone(), key, None).is_ok() { + return Err(YamlError::KeyShadowing(key.to_string())); + } + + let address = Token::validate_address(address)?; + let decimals = decimals.map(Token::validate_decimals).transpose()?; + Network::parse_from_yaml(documents.clone(), network_key, None)?; + + let mut document = documents[0] + .write() + .map_err(|_| YamlError::WriteLockError)?; + + if let StrictYaml::Hash(ref mut document_hash) = *document { + if !document_hash.contains_key(&StrictYaml::String("tokens".to_string())) + || document_hash + .get_mut(&StrictYaml::String("tokens".to_string())) + .is_none() + { + document_hash.insert( + StrictYaml::String("tokens".to_string()), + StrictYaml::Hash(Hash::new()), + ); + } + + if let Some(StrictYaml::Hash(ref mut tokens)) = + document_hash.get_mut(&StrictYaml::String("tokens".to_string())) + { + if tokens.contains_key(&StrictYaml::String(key.to_string())) { + return Err(YamlError::KeyShadowing(key.to_string())); + } + + let mut token_hash = Hash::new(); + token_hash.insert( + StrictYaml::String("network".to_string()), + StrictYaml::String(network_key.to_string()), + ); + token_hash.insert( + StrictYaml::String("address".to_string()), + StrictYaml::String(address.to_string()), + ); + if let Some(decimals) = decimals { + token_hash.insert( + StrictYaml::String("decimals".to_string()), + StrictYaml::String(decimals.to_string()), + ); + } + if let Some(label) = label { + token_hash.insert( + StrictYaml::String("label".to_string()), + StrictYaml::String(label.to_string()), + ); + } + if let Some(symbol) = symbol { + token_hash.insert( + StrictYaml::String("symbol".to_string()), + StrictYaml::String(symbol.to_string()), + ); + } + + tokens.insert( + StrictYaml::String(key.to_string()), + StrictYaml::Hash(token_hash), + ); + } else { + return Err(YamlError::ParseError("missing field: token".to_string())); + } + } else { + return Err(YamlError::ParseError("document parse error".to_string())); + } + + Ok(()) + } + + pub fn remove_record_from_yaml( + documents: Vec>>, + key: &str, + ) -> Result<(), YamlError> { + for document in documents { + let mut document_write = document.write().map_err(|_| YamlError::WriteLockError)?; + + if let StrictYaml::Hash(ref mut document_hash) = *document_write { + if let Some(StrictYaml::Hash(ref mut tokens)) = + document_hash.get_mut(&StrictYaml::String("tokens".to_string())) + { + if tokens.contains_key(&StrictYaml::String(key.to_string())) { + tokens.remove(&StrictYaml::String(key.to_string())); + return Ok(()); + } + } + } + } + + Err(YamlError::KeyNotFound(key.to_string())) + } } impl YamlParsableHash for Token { fn parse_all_from_yaml( diff --git a/crates/settings/src/yaml/context.rs b/crates/settings/src/yaml/context.rs index fffa3d67a..bc2cd88eb 100644 --- a/crates/settings/src/yaml/context.rs +++ b/crates/settings/src/yaml/context.rs @@ -5,6 +5,7 @@ use thiserror::Error; #[derive(Debug, Clone, Default)] pub struct Context { pub order: Option>, + pub select_tokens: Option>, } #[derive(Error, Debug, PartialEq)] @@ -35,6 +36,22 @@ pub trait OrderContext { fn resolve_token_path(&self, token: &Token, parts: &[&str]) -> Result; } +pub trait SelectTokensContext { + fn select_tokens(&self) -> Option<&Vec>; + + fn is_select_token(&self, key: &str) -> bool { + self.select_tokens() + .map(|tokens| tokens.iter().any(|t| t == key)) + .unwrap_or(false) + } +} + +impl SelectTokensContext for Context { + fn select_tokens(&self) -> Option<&Vec> { + self.select_tokens.as_ref() + } +} + impl OrderContext for Context { fn order(&self) -> Option<&Arc> { self.order.as_ref() @@ -52,7 +69,10 @@ impl OrderContext for Context { .ok_or_else(|| ContextError::InvalidIndex(index.to_string()))?; match parts.get(1) { - Some(&"token") => self.resolve_token_path(&io.token, &parts[2..]), + Some(&"token") => match &io.token { + Some(token) => self.resolve_token_path(token, &parts[2..]), + None => Err(ContextError::PropertyNotFound("token".to_string())), + }, Some(&"vault-id") => match &io.vault_id { Some(vault_id) => Ok(vault_id.to_string()), None => Err(ContextError::PropertyNotFound("vault-id".to_string())), @@ -83,11 +103,20 @@ impl OrderContext for Context { impl Context { pub fn new() -> Self { - Self { order: None } + Self { + order: None, + select_tokens: None, + } + } + + pub fn add_order(&mut self, order: Arc) -> &mut Self { + self.order = Some(order); + self } - pub fn with_order(order: Arc) -> Self { - Self { order: Some(order) } + pub fn add_select_tokens(&mut self, select_tokens: Vec) -> &mut Self { + self.select_tokens = Some(select_tokens); + self } fn resolve_path(&self, path: &str) -> Result { @@ -144,11 +173,11 @@ mod tests { document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), key: "test_order".to_string(), inputs: vec![OrderIO { - token: Arc::new(token.clone()), + token: Some(Arc::new(token.clone())), vault_id: Some(U256::from(42)), }], outputs: vec![OrderIO { - token: Arc::new(token), + token: Some(Arc::new(token.clone())), vault_id: None, }], network: mock_network(), @@ -160,7 +189,8 @@ mod tests { #[test] fn test_context_interpolation() { let order = setup_test_order_with_vault_id(); - let context = Context::with_order(order.clone()); + let mut context = Context::new(); + context.add_order(order.clone()); // Test basic interpolation assert_eq!( diff --git a/crates/settings/src/yaml/dotrain.rs b/crates/settings/src/yaml/dotrain.rs index 8507a754a..4005fa9d4 100644 --- a/crates/settings/src/yaml/dotrain.rs +++ b/crates/settings/src/yaml/dotrain.rs @@ -285,12 +285,15 @@ mod tests { assert_eq!(order.inputs.len(), 1); let input = order.inputs.first().unwrap(); assert_eq!( - *input.token.clone().as_ref(), - ob_yaml.get_token("token1").unwrap() + *input.token.clone().as_ref().unwrap(), + ob_yaml.get_token("token1").unwrap().into() ); assert_eq!(input.vault_id, Some(U256::from(1))); let output = order.outputs.first().unwrap(); - assert_eq!(*output.token.as_ref(), ob_yaml.get_token("token2").unwrap()); + assert_eq!( + *output.token.as_ref().unwrap(), + ob_yaml.get_token("token2").unwrap().into() + ); assert_eq!(output.vault_id, Some(U256::from(2))); assert_eq!( *order.network.as_ref(), @@ -349,8 +352,8 @@ mod tests { assert_eq!(deployment.deposits.len(), 1); let deposit = &deployment.deposits[0]; assert_eq!( - *deposit.token.as_ref(), - ob_yaml.get_token("token1").unwrap() + *deposit.token.as_ref().unwrap(), + ob_yaml.get_token("token1").unwrap().into() ); assert_eq!(deposit.presets.len(), 2); assert_eq!(deposit.presets[0], "100".to_string()); @@ -566,4 +569,94 @@ mod tests { Some("With token symbol WETH".to_string()) ); } + + #[test] + fn test_parse_orders_missing_token() { + let yaml_prefix = r#" +networks: + mainnet: + rpc: https://mainnet.infura.io + chain-id: 1 +deployers: + mainnet: + address: 0x0000000000000000000000000000000000000001 + network: mainnet +scenarios: + scenario1: + deployer: mainnet + bindings: + key1: value1 +deployments: + deployment1: + order: order1 + scenario: scenario1 +gui: + name: test + description: test + deployments: + deployment1: + name: test + description: test + deposits: + - token: token-one + presets: + - 1 + - token: token-two + presets: + - 1 + - token: token-three + presets: + - 1 + fields: + - binding: key1 + name: test + presets: + - value: 1 + select-tokens: + - token-one + - token-two +"#; + let missing_input_token_yaml = format!( + "{yaml_prefix} +orders: + order1: + inputs: + - token: token-three + outputs: + - token: token-two + - token: token-three + " + ); + let missing_output_token_yaml = format!( + "{yaml_prefix} +orders: + order1: + inputs: + - token: token-one + - token: token-two + outputs: + - token: token-three + " + ); + + let dotrain_yaml = DotrainYaml::new(vec![missing_input_token_yaml], false).unwrap(); + let error = dotrain_yaml.get_gui().unwrap_err(); + assert_eq!( + error, + YamlError::ParseError( + "yaml data for token: token-three not found in input index: 0 in order: order1" + .to_string() + ) + ); + + let dotrain_yaml = DotrainYaml::new(vec![missing_output_token_yaml], false).unwrap(); + let error = dotrain_yaml.get_gui().unwrap_err(); + assert_eq!( + error, + YamlError::ParseError( + "yaml data for token: token-three not found in output index: 0 in order: order1" + .to_string() + ) + ); + } } diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index 5987854cc..2f96feb4f 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -355,6 +355,48 @@ mod tests { ); } + #[test] + fn test_add_token_to_yaml() { + let yaml = r#" +networks: + mainnet: + rpc: "https://mainnet.infura.io" + chain-id: "1" +"#; + let ob_yaml = OrderbookYaml::new(vec![yaml.to_string()], false).unwrap(); + + Token::add_record_to_yaml( + ob_yaml.documents.clone(), + "test-token", + "mainnet", + "0x0000000000000000000000000000000000000001", + Some("18"), + Some("Test Token"), + Some("TTK"), + ) + .unwrap(); + + let token = ob_yaml.get_token("test-token").unwrap(); + assert_eq!(token.key, "test-token"); + assert_eq!(token.network.key, "mainnet"); + assert_eq!( + token.address, + Address::from_str("0x0000000000000000000000000000000000000001").unwrap() + ); + assert_eq!(token.decimals, Some(18)); + assert_eq!(token.label, Some("Test Token".to_string())); + assert_eq!(token.symbol, Some("TTK".to_string())); + } + + #[test] + fn test_remove_token_from_yaml() { + let ob_yaml = OrderbookYaml::new(vec![FULL_YAML.to_string()], false).unwrap(); + + assert!(ob_yaml.get_token("token1").is_ok()); + Token::remove_record_from_yaml(ob_yaml.documents.clone(), "token1").unwrap(); + assert!(ob_yaml.get_token("token1").is_err()); + } + #[test] fn test_add_metaboard_to_yaml() { let yaml = r#" diff --git a/packages/orderbook/test/js_api/gui.test.ts b/packages/orderbook/test/js_api/gui.test.ts index 9115eb9e6..e47465038 100644 --- a/packages/orderbook/test/js_api/gui.test.ts +++ b/packages/orderbook/test/js_api/gui.test.ts @@ -12,9 +12,8 @@ import { Gui, GuiDeployment, GuiDetails, - SelectTokens, TokenDeposit, - TokenInfos + TokenInfo } from '../../dist/types/js_api.js'; import { getLocal } from 'mockttp'; @@ -273,6 +272,60 @@ _ _: 0 0; #handle-add-order :; `; +const dotrainWithoutTokens = ` +networks: + some-network: + rpc: http://localhost:8085/rpc-url + chain-id: 123 + network-id: 123 + currency: ETH + +subgraphs: + some-sg: https://www.some-sg.com +metaboards: + test: https://metaboard.com + +deployers: + some-deployer: + network: some-network + address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba + +orderbooks: + some-orderbook: + address: 0xc95A5f8eFe14d7a20BD2E5BAFEC4E71f8Ce0B9A6 + network: some-network + subgraph: some-sg + +scenarios: + some-scenario: + deployer: some-deployer + bindings: + test-binding: 5 + +orders: + some-order: + inputs: + - token: token1 + outputs: + - token: token2 + deployer: some-deployer + orderbook: some-orderbook + +deployments: + some-deployment: + scenario: some-scenario + order: some-order + other-deployment: + scenario: some-scenario + order: some-order +--- +#calculate-io +_ _: 0 0; +#handle-io +:; +#handle-add-order +:; +`; const dotrainWithGui = ` ${guiConfig} @@ -347,15 +400,16 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () `; const gui = await DotrainOrderGui.chooseDeployment(dotrainWithGui, 'other-deployment'); - const tokenInfos: TokenInfos = gui.getTokenInfos(); - const token1Address = '0xc2132d05d31c914a87c6611c10748aeb04b58e8f'; - const token2Address = '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063'; - assert.equal(tokenInfos.get(token1Address)?.decimals, 6); - assert.equal(tokenInfos.get(token1Address)?.name, 'Token 1'); - assert.equal(tokenInfos.get(token1Address)?.symbol, 'T1'); - assert.equal(tokenInfos.get(token2Address)?.decimals, 18); - assert.equal(tokenInfos.get(token2Address)?.name, 'Token 2'); - assert.equal(tokenInfos.get(token2Address)?.symbol, 'T2'); + let token1TokenInfo = await gui.getTokenInfo('token1'); + let token2TokenInfo = await gui.getTokenInfo('token2'); + assert.equal(token1TokenInfo.address, '0xc2132d05d31c914a87c6611c10748aeb04b58e8f'); + assert.equal(token1TokenInfo.decimals, 6); + assert.equal(token1TokenInfo.name, 'Token 1'); + assert.equal(token1TokenInfo.symbol, 'T1'); + assert.equal(token2TokenInfo.address, '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063'); + assert.equal(token2TokenInfo.decimals, 18); + assert.equal(token2TokenInfo.name, 'Token 2'); + assert.equal(token2TokenInfo.symbol, 'T2'); }); describe('deposit tests', async () => { @@ -942,7 +996,7 @@ ${dotrainWithoutVaultIds}`; let testDotrain = ` ${guiConfig3} - ${dotrain} + ${dotrainWithoutTokens} `; let testGui = await DotrainOrderGui.chooseDeployment(testDotrain, 'other-deployment'); @@ -1027,16 +1081,16 @@ ${dotrainWithoutVaultIds}`; let dotrain3 = ` ${guiConfig3} - ${dotrain} + ${dotrainWithoutTokens} `; gui = await DotrainOrderGui.chooseDeployment(dotrain3, 'other-deployment'); }); it('should get select tokens', async () => { - const selectTokens: SelectTokens = gui.getSelectTokens(); - assert.equal(selectTokens.size, 2); - assert.equal(selectTokens.get('token1'), '0x0000000000000000000000000000000000000000'); - assert.equal(selectTokens.get('token2'), '0x0000000000000000000000000000000000000000'); + const selectTokens: string[] = gui.getSelectTokens(); + assert.equal(selectTokens.length, 2); + assert.equal(selectTokens[0], 'token1'); + assert.equal(selectTokens[1], 'token2'); }); it('should throw error if select tokens not set', async () => { @@ -1049,14 +1103,14 @@ ${dotrainWithoutVaultIds}`; ); let testGui = await DotrainOrderGui.chooseDeployment(dotrainWithGui, 'some-deployment'); - expect(() => testGui.getSelectTokens()).toThrow('Select tokens not set'); - await expect( - async () => await testGui.saveSelectTokenAddress('token1', '0x1') - ).rejects.toThrow('Select tokens not set'); + assert.equal(testGui.getSelectTokens().length, 0); + await expect(async () => await testGui.saveSelectToken('token1', '0x1')).rejects.toThrow( + 'Select tokens not set' + ); }); it('should throw error if token not found', async () => { - await expect(async () => await gui.saveSelectTokenAddress('token3', '0x1')).rejects.toThrow( + await expect(async () => await gui.saveSelectToken('token3', '0x1')).rejects.toThrow( 'Token not found' ); }); @@ -1078,41 +1132,67 @@ ${dotrainWithoutVaultIds}`; '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' ); - let initialTokenInfo: TokenInfos = await gui.getTokenInfos(); - assert.equal(initialTokenInfo.size, 0); + assert.equal(gui.isSelectTokenSet('token1'), false); + assert.equal(gui.isSelectTokenSet('token2'), false); - let currentDeployment: GuiDeployment = gui.getCurrentDeployment(); - assert.equal( - currentDeployment.deployment.order.inputs[0].token.address, - '0xc2132d05d31c914a87c6611c10748aeb04b58e8f' + await expect(async () => await gui.getTokenInfo('token1')).rejects.toThrow( + 'Yaml parse error: missing field: token' ); - assert.equal( - currentDeployment.deployment.order.outputs[0].token.address, - '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063' + await expect(async () => await gui.getTokenInfo('token2')).rejects.toThrow( + 'Yaml parse error: missing field: token' ); - await gui.saveSelectTokenAddress('token1', '0x6666666666666666666666666666666666666666'); - await gui.saveSelectTokenAddress('token2', '0x8888888888888888888888888888888888888888'); - assert.equal( - gui.getSelectTokens().get('token1'), - '0x6666666666666666666666666666666666666666' - ); - assert.equal( - gui.getSelectTokens().get('token2'), - '0x8888888888888888888888888888888888888888' - ); + await gui.saveSelectToken('token1', '0x6666666666666666666666666666666666666666'); + await gui.saveSelectToken('token2', '0x8888888888888888888888888888888888888888'); - let tokenInfo: TokenInfos = await gui.getTokenInfos(); - assert.equal(tokenInfo.size, 2); + assert.equal(gui.isSelectTokenSet('token1'), true); + assert.equal(gui.isSelectTokenSet('token2'), true); - let newCurrentDeployment: GuiDeployment = gui.getCurrentDeployment(); - assert.equal( - newCurrentDeployment.deployment.order.inputs[0].token.address, - '0x6666666666666666666666666666666666666666' - ); - assert.equal( - newCurrentDeployment.deployment.order.outputs[0].token.address, - '0x8888888888888888888888888888888888888888' + let tokenInfo: TokenInfo = await gui.getTokenInfo('token1'); + assert.equal(tokenInfo.name, 'Token 1'); + assert.equal(tokenInfo.symbol, 'T1'); + assert.equal(tokenInfo.decimals, 6); + + tokenInfo = await gui.getTokenInfo('token2'); + assert.equal(tokenInfo.name, 'Teken 2'); + assert.equal(tokenInfo.symbol, 'T2'); + assert.equal(tokenInfo.decimals, 18); + }); + + it('should remove select token', async () => { + let dotrain3 = ` + ${guiConfig3} + + ${dotrainWithoutTokens} + `; + gui = await DotrainOrderGui.chooseDeployment(dotrain3, 'other-deployment'); + + mockServer + .forPost('/rpc-url') + .once() + .withBodyIncluding('0x82ad56cb') + .thenSendJsonRpcResult( + '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000' + ); + mockServer + .forPost('/rpc-url') + .once() + .withBodyIncluding('0x82ad56cb') + .thenSendJsonRpcResult( + '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' + ); + + await gui.saveSelectToken('token1', '0x6666666666666666666666666666666666666666'); + assert.equal(gui.isSelectTokenSet('token1'), true); + let tokenInfo = await gui.getTokenInfo('token1'); + assert.equal(tokenInfo.name, 'Token 1'); + assert.equal(tokenInfo.symbol, 'T1'); + assert.equal(tokenInfo.decimals, 6); + + gui.removeSelectToken('token1'); + assert.equal(gui.isSelectTokenSet('token1'), false); + await expect(async () => await gui.getTokenInfo('token1')).rejects.toThrow( + 'Yaml parse error: missing field: token' ); }); }); diff --git a/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte b/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte index 5384a7838..e6f95b116 100644 --- a/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte +++ b/packages/ui-components/src/lib/components/tables/OrdersListTable.svelte @@ -122,7 +122,6 @@ >Output Token(s) Trades - diff --git a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte index 28c8d7a3a..57aa95cc3 100644 --- a/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte +++ b/packages/ui-components/src/lib/components/tables/VaultsListTable.svelte @@ -138,7 +138,6 @@ Balance Input For Output For - diff --git a/packages/webapp/src/routes/deployment/+page.svelte b/packages/webapp/src/routes/deployment/+page.svelte deleted file mode 100644 index c9a40f161..000000000 --- a/packages/webapp/src/routes/deployment/+page.svelte +++ /dev/null @@ -1,401 +0,0 @@ - - -
- { - gui = undefined; - }} - /> -
- -
- - - - {#if selectedRef === undefined} - Select a deployment - {:else if selectedOption?.label} - {selectedOption.label} - {:else} - {selectedRef} - {/if} - - - -
-
{option.label ? option.label : ref}
-
-
-
-
- -{#if gui} - {#if isLimitStrat && selectTokens.size > 0} - - - {#each selectTokens.entries() as [token]} -
- - - { - if (currentTarget instanceof HTMLInputElement) { - if (!gui) return; - await gui.saveSelectTokenAddress(token, currentTarget.value); - selectTokens = gui.getSelectTokens(); - gui = gui; - } - }} - /> -
- {/each} - {/if} - - {#if allFieldDefinitions.length > 0} - - - {#each allFieldDefinitions as fieldDefinition} -
- - - [ - preset.id, - { - label: preset.name, - id: preset.id - } - ]) - ), - ...{ custom: { label: 'Custom value', id: '' } } - }} - on:change={({ detail }) => { - gui?.saveFieldValue(fieldDefinition.binding, { - isPreset: detail.value !== 'custom', - value: detail.value === 'custom' ? '' : detail.value || '' - }); - gui = gui; - }} - > - - {#if selectedRef === undefined} - Select a preset - {:else if selectedOption?.label} - {selectedOption.label} - {:else} - {selectedRef} - {/if} - - - -
-
{option.label ? option.label : ref}
-
-
-
- - {#if gui?.isFieldPreset(fieldDefinition.binding) === false} - { - if (currentTarget instanceof HTMLInputElement) { - gui?.saveFieldValue(fieldDefinition.binding, { - isPreset: false, - value: currentTarget.value - }); - } - }} - /> - {/if} -
- {/each} - {/if} - - {#if allDeposits.length > 0} - - - {#each allDeposits as deposit} -
- - - [ - preset, - { - label: preset - } - ]) - ), - ...{ custom: { label: 'Custom value' } } - }} - on:change={({ detail }) => { - gui?.saveDeposit( - deposit.token.key, - detail.value === 'custom' ? '' : detail.value || '' - ); - gui = gui; - }} - > - - {#if selectedRef === undefined} - Choose deposit amount - {:else if selectedOption?.label} - {selectedOption.label} - {:else} - {selectedRef} - {/if} - - - -
-
{option.label ? option.label : ref}
-
-
-
- - {#if gui?.isDepositPreset(deposit.token.key) === false} - { - if (currentTarget instanceof HTMLInputElement) { - gui?.saveDeposit(deposit.token.key, currentTarget.value); - } - }} - /> - {/if} -
- {/each} - {/if} - - {#if selectedDeployment} -
-
- -
- - {#if useCustomVaultIds} - - - {#if gui?.getCurrentDeployment().deployment.order.inputs.length > 0} - - {#each gui?.getCurrentDeployment().deployment.order.inputs as input, i} -
- - gui?.setVaultId(true, i, inputVaultIds[i])} - /> -
- {/each} - {/if} - - {#if gui?.getCurrentDeployment().deployment.order.outputs.length > 0} - - {#each gui?.getCurrentDeployment().deployment.order.outputs as output, i} -
- - gui?.setVaultId(false, i, outputVaultIds[i])} - /> -
- {/each} - {/if} - {/if} -
- - - {/if} -{/if}