Skip to content

Commit

Permalink
Merge pull request #1212 from rainlanguage/2025-01-23-state-management
Browse files Browse the repository at this point in the history
  • Loading branch information
hardyjosh authored Feb 5, 2025
2 parents f9f4ef8 + 884ab0c commit e65e965
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"svelte.enable-ts-plugin": true,

"rust-analyzer.linkedProjects": ["./Cargo.toml", "tauri-app/src-tauri/Cargo.toml"],

"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true,
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/js_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ alloy = { workspace = true, features = [ "dyn-abi" ] }
flate2 = "1.0.34"
base64 = "0.22.1"
bincode = "1.3.3"
sha2 = "0.10.8"
web-sys = { version = "0.3.69", features = ["console"] }
4 changes: 2 additions & 2 deletions crates/js_api/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ pub enum GuiError {
DepositTokenNotFound(String),
#[error("Orderbook not found")]
OrderbookNotFound,
#[error("Deserialized config mismatch")]
DeserializedConfigMismatch,
#[error("Deserialized dotrain mismatch")]
DotrainMismatch,
#[error("Vault id not found for output index: {0}")]
VaultIdNotFound(String),
#[error("Deployer not found")]
Expand Down
103 changes: 78 additions & 25 deletions crates/js_api/src/gui/state_management.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
use super::*;
use rain_orderbook_app_settings::token::Token;
use sha2::{Digest, Sha256};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct SerializedGuiState {
field_values: BTreeMap<String, GuiPreset>,
deposits: BTreeMap<String, GuiPreset>,
select_tokens: BTreeMap<String, Token>,
vault_ids: BTreeMap<(bool, u8), Option<String>>,
dotrain_hash: String,
selected_deployment: String,
}

#[wasm_bindgen]
impl DotrainOrderGui {
fn get_dotrain_hash(dotrain: &str) -> Result<String, GuiError> {
let dotrain_bytes = bincode::serialize(dotrain)?;
let hash = Sha256::digest(&dotrain_bytes);
Ok(URL_SAFE.encode(hash))
}

#[wasm_bindgen(js_name = "serializeState")]
pub fn serialize_state(&self) -> Result<String, GuiError> {
let deployment = self.get_current_deployment()?;

let mut field_values = BTreeMap::new();
for (k, v) in self.field_values.iter() {
let preset = if v.is_preset {
let field_definition = self.get_field_definition(k)?;
let presets = field_definition
.presets
.ok_or(GuiError::BindingHasNoPresets(k.clone()))?;
let presets = Gui::parse_field_presets(
self.dotrain_order.dotrain_yaml().documents.clone(),
&self.selected_deployment,
k,
)?
.ok_or(GuiError::BindingHasNoPresets(k.clone()))?;
presets
.iter()
.find(|preset| preset.id == v.value)
Expand Down Expand Up @@ -56,26 +65,50 @@ impl DotrainOrderGui {
}

let mut select_tokens: BTreeMap<String, Token> = BTreeMap::new();
if let Some(st) = deployment.select_tokens {
if let Some(st) = Gui::parse_select_tokens(
self.dotrain_order.dotrain_yaml().documents.clone(),
&self.selected_deployment,
)? {
for key in st {
let token = self.dotrain_order.orderbook_yaml().get_token(&key)?;
select_tokens.insert(key, token);
if let Ok(token) = self.dotrain_order.orderbook_yaml().get_token(&key) {
select_tokens.insert(key, token);
}
}
}

let order_key = Deployment::parse_order_key(
self.dotrain_order.dotrain_yaml().documents,
&self.selected_deployment,
)?;
let mut vault_ids = BTreeMap::new();
for (i, input) in deployment.deployment.order.inputs.iter().enumerate() {
vault_ids.insert((true, i as u8), input.vault_id.map(|v| v.to_string()));
for (i, vault_id) in Order::parse_vault_ids(
self.dotrain_order.dotrain_yaml().documents.clone(),
&order_key,
true,
)?
.iter()
.enumerate()
{
vault_ids.insert((true, i as u8), vault_id.as_ref().map(|v| v.to_string()));
}
for (i, output) in deployment.deployment.order.outputs.iter().enumerate() {
vault_ids.insert((false, i as u8), output.vault_id.map(|v| v.to_string()));
for (i, vault_id) in Order::parse_vault_ids(
self.dotrain_order.dotrain_yaml().documents.clone(),
&order_key,
false,
)?
.iter()
.enumerate()
{
vault_ids.insert((false, i as u8), vault_id.as_ref().map(|v| v.to_string()));
}

let state = SerializedGuiState {
field_values: field_values.clone(),
deposits: deposits.clone(),
select_tokens: select_tokens.clone(),
vault_ids: vault_ids.clone(),
dotrain_hash: DotrainOrderGui::get_dotrain_hash(&self.dotrain_order.dotrain())?,
selected_deployment: self.selected_deployment.clone(),
};
let bytes = bincode::serialize(&state)?;

Expand All @@ -87,16 +120,24 @@ impl DotrainOrderGui {
}

#[wasm_bindgen(js_name = "deserializeState")]
pub fn deserialize_state(&mut self, serialized: String) -> Result<(), GuiError> {
let deployment = self.get_current_deployment()?;
pub async fn deserialize_state(
dotrain: String,
serialized: String,
) -> Result<DotrainOrderGui, GuiError> {
let compressed = URL_SAFE.decode(serialized)?;

let mut decoder = GzDecoder::new(&compressed[..]);
let mut bytes = Vec::new();
decoder.read_to_end(&mut bytes)?;

let original_dotrain_hash = DotrainOrderGui::get_dotrain_hash(&dotrain)?;
let state: SerializedGuiState = bincode::deserialize(&bytes)?;

if original_dotrain_hash != state.dotrain_hash {
return Err(GuiError::DotrainMismatch);
}
let dotrain_order = DotrainOrder::new(dotrain, None).await?;

let field_values = state
.field_values
.into_iter()
Expand Down Expand Up @@ -135,25 +176,32 @@ impl DotrainOrderGui {
})
.collect::<BTreeMap<_, _>>();

self.field_values = field_values;
self.deposits = deposits;
let dotrain_order_gui = DotrainOrderGui {
dotrain_order,
field_values,
deposits,
selected_deployment: state.selected_deployment.clone(),
};

let deployment_select_tokens = Gui::parse_select_tokens(
dotrain_order_gui.dotrain_order.dotrain_yaml().documents,
&state.selected_deployment,
)?;
for (key, token) in state.select_tokens {
let select_tokens = deployment
.select_tokens
let select_tokens = deployment_select_tokens
.as_ref()
.ok_or(GuiError::SelectTokensNotSet)?;
if !select_tokens.contains(&key) {
return Err(GuiError::TokenNotInSelectTokens(key));
}
if self.is_select_token_set(key.clone())? {
if dotrain_order_gui.is_select_token_set(key.clone())? {
Token::remove_record_from_yaml(
self.dotrain_order.orderbook_yaml().documents.clone(),
dotrain_order_gui.dotrain_order.orderbook_yaml().documents,
&key,
)?;
}
Token::add_record_to_yaml(
self.dotrain_order.orderbook_yaml().documents.clone(),
dotrain_order_gui.dotrain_order.orderbook_yaml().documents,
&key,
&token.network.key,
&token.address.to_string(),
Expand All @@ -163,16 +211,21 @@ impl DotrainOrderGui {
)?;
}

let order_key = Deployment::parse_order_key(
dotrain_order_gui.dotrain_order.dotrain_yaml().documents,
&state.selected_deployment,
)?;
for ((is_input, index), vault_id) in state.vault_ids {
self.dotrain_order
dotrain_order_gui
.dotrain_order
.dotrain_yaml()
.get_order(&deployment.deployment.order.key)
.get_order(&order_key)
.and_then(|mut order| {
order.update_vault_id(is_input, index, vault_id.unwrap_or_default())
})?;
}

Ok(())
Ok(dotrain_order_gui)
}

#[wasm_bindgen(js_name = "clearState")]
Expand Down
72 changes: 72 additions & 0 deletions crates/settings/src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,78 @@ impl Gui {

Ok(deployment_details)
}

pub fn parse_field_presets(
documents: Vec<Arc<RwLock<StrictYaml>>>,
deployment_key: &str,
field_binding: &str,
) -> Result<Option<Vec<GuiPreset>>, YamlError> {
for document in documents {
let document_read = document.read().map_err(|_| YamlError::ReadLockError)?;

if let Some(gui) = optional_hash(&document_read, "gui") {
if let Some(StrictYaml::Hash(deployments_hash)) =
gui.get(&StrictYaml::String("deployments".to_string()))
{
if let Some(StrictYaml::Hash(deployment_hash)) =
deployments_hash.get(&StrictYaml::String(deployment_key.to_string()))
{
if let Some(StrictYaml::Array(fields)) =
deployment_hash.get(&StrictYaml::String("fields".to_string()))
{
for (field_index, field) in fields.iter().enumerate() {
if let StrictYaml::Hash(field_hash) = field {
if let Some(StrictYaml::String(binding)) =
field_hash.get(&StrictYaml::String("binding".to_string()))
{
if binding == field_binding {
return match optional_vec(field, "presets") {
Some(presets) => {
let preset_vec = presets.iter().enumerate()
.map(|(preset_index, preset_yaml)| {
let name = optional_string(preset_yaml, "name");
let value = require_string(
preset_yaml,
Some("value"),
Some(format!(
"preset value must be a string for preset index: {preset_index} for field index: {field_index} in gui deployment: {deployment_key}",
))
)?;

Ok(GuiPreset {
id: preset_index.to_string(),
name,
value,
})
})
.collect::<Result<Vec<_>, YamlError>>()?;
Ok(Some(preset_vec))
}
None => Ok(None),
};
}
} else {
return Err(YamlError::ParseError(format!(
"binding string missing for field index: {field_index} in gui deployment: {deployment_key}",
)));
}
}
}
} else {
return Err(YamlError::ParseError(format!(
"fields list missing in gui deployment: {deployment_key}"
)));
}
}
} else {
return Err(YamlError::ParseError(
"deployments field must be a map in gui".to_string(),
));
}
}
}
Ok(None)
}
}

impl YamlParseableValue for Gui {
Expand Down
43 changes: 43 additions & 0 deletions crates/settings/src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,49 @@ impl Order {
))?,
)
}

pub fn parse_vault_ids(
documents: Vec<Arc<RwLock<StrictYaml>>>,
order_key: &str,
is_input: bool,
) -> Result<Vec<Option<String>>, YamlError> {
let mut vault_ids = Vec::new();

for document in documents {
let document_read = document.read().map_err(|_| YamlError::ReadLockError)?;

if let Ok(orders_hash) = require_hash(&document_read, Some("orders"), None) {
if let Some(order_yaml) =
orders_hash.get(&StrictYaml::String(order_key.to_string()))
{
let items = if is_input {
require_vec(
order_yaml,
"inputs",
Some(format!("inputs list missing in order: {order_key}")),
)?
} else {
require_vec(
order_yaml,
"outputs",
Some(format!("outputs list missing in order: {order_key}")),
)?
};

for item in items {
let vault_id = optional_string(item, "vault-id");
vault_ids.push(vault_id);
}
}
} else {
return Err(YamlError::ParseError(
"orders field must be a map".to_string(),
));
}
}

Ok(vault_ids)
}
}

impl YamlParsableHash for Order {
Expand Down
16 changes: 16 additions & 0 deletions crates/settings/src/yaml/dotrain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,14 @@ mod tests {
*order.network.as_ref(),
ob_yaml.get_network("mainnet").unwrap()
);
let input_vault_ids =
Order::parse_vault_ids(dotrain_yaml.documents.clone(), &order.key, true).unwrap();
assert_eq!(input_vault_ids.len(), 1);
assert_eq!(input_vault_ids[0], Some("1".to_string()));
let output_vault_ids =
Order::parse_vault_ids(dotrain_yaml.documents.clone(), &order.key, false).unwrap();
assert_eq!(output_vault_ids.len(), 1);
assert_eq!(output_vault_ids[0], Some("2".to_string()));

let scenario_keys = dotrain_yaml.get_scenario_keys().unwrap();
assert_eq!(scenario_keys.len(), 3);
Expand Down Expand Up @@ -413,6 +421,14 @@ mod tests {
let select_tokens =
Gui::parse_select_tokens(dotrain_yaml.documents.clone(), "deployment2").unwrap();
assert!(select_tokens.is_none());

let field_presets =
Gui::parse_field_presets(dotrain_yaml.documents.clone(), "deployment1", "key1")
.unwrap()
.unwrap();
assert_eq!(field_presets[0].id, "0");
assert_eq!(field_presets[0].name, None);
assert_eq!(field_presets[0].value, "value2");
}

#[test]
Expand Down
Loading

0 comments on commit e65e965

Please sign in to comment.