From 0ad5e513849305c33bf6bea93c314ba5fefad2f2 Mon Sep 17 00:00:00 2001 From: findolor Date: Tue, 12 Nov 2024 09:19:02 +0300 Subject: [PATCH 1/8] add dependencies for serialize/deserialize --- Cargo.lock | 30 ++++++++++++++++++++++++------ crates/js_api/Cargo.toml | 5 ++++- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a931b96f..d8230c8fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "adler32" version = "1.2.0" @@ -1637,7 +1643,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -3405,12 +3411,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -5210,6 +5216,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.2" @@ -6424,7 +6439,9 @@ name = "rain_orderbook_js_api" version = "0.0.0-alpha.0" dependencies = [ "alloy", + "base64 0.22.1", "cynic", + "flate2", "js-sys", "rain_orderbook_app_settings", "rain_orderbook_bindings", @@ -6433,6 +6450,7 @@ dependencies = [ "reqwest 0.12.5", "serde", "serde-wasm-bindgen 0.6.5", + "serde_json", "thiserror", "tokio", "tsify", @@ -7513,9 +7531,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "indexmap", "itoa", diff --git a/crates/js_api/Cargo.toml b/crates/js_api/Cargo.toml index 48a2fa80c..f8ec46b78 100644 --- a/crates/js_api/Cargo.toml +++ b/crates/js_api/Cargo.toml @@ -25,4 +25,7 @@ serde-wasm-bindgen = { version = "0.6.5" } wasm-bindgen-futures = { version = "0.4.42" } tsify = { version = "0.4.5", default-features = false, features = ["js", "wasm-bindgen"] } tokio = { workspace = true, features = ["sync", "macros", "io-util", "rt", "time"] } -alloy = { workspace = true, features = [ "dyn-abi" ] } \ No newline at end of file +alloy = { workspace = true, features = [ "dyn-abi" ] } +flate2 = "1.0.34" +base64 = "0.22.1" +serde_json = "1.0.132" \ No newline at end of file From 76ff37177d1fe10ea6713ecd30486f5931ec2665 Mon Sep 17 00:00:00 2001 From: findolor Date: Tue, 12 Nov 2024 09:19:14 +0300 Subject: [PATCH 2/8] implement state management --- crates/js_api/src/gui/mod.rs | 57 ++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/crates/js_api/src/gui/mod.rs b/crates/js_api/src/gui/mod.rs index 76cb38d50..80690eb16 100644 --- a/crates/js_api/src/gui/mod.rs +++ b/crates/js_api/src/gui/mod.rs @@ -1,4 +1,6 @@ use alloy::primitives::Address; +use base64::{engine::general_purpose::URL_SAFE, Engine}; +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use rain_orderbook_app_settings::gui::{ Gui, GuiDeployment, GuiFieldDefinition, ParseGuiConfigSourceError, }; @@ -6,7 +8,8 @@ use rain_orderbook_bindings::impl_wasm_traits; use rain_orderbook_common::dotrain_order::{DotrainOrder, DotrainOrderError}; use serde::{Deserialize, Serialize}; use serde_wasm_bindgen::{from_value, to_value}; -use std::collections::HashMap; +use std::collections::BTreeMap; +use std::io::prelude::*; use thiserror::Error; use tsify::Tsify; use wasm_bindgen::{ @@ -36,12 +39,18 @@ pub struct FieldValuePair { } impl_wasm_traits!(FieldValuePair); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +struct SerializedGuiState { + field_values: BTreeMap, + deposits: Vec, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[wasm_bindgen] pub struct DotrainOrderGui { dotrain_order: DotrainOrder, deployment: GuiDeployment, - field_values: HashMap, + field_values: BTreeMap, deposits: Vec, } #[wasm_bindgen] @@ -65,7 +74,7 @@ impl DotrainOrderGui { Ok(Self { dotrain_order, deployment: gui_deployment.clone(), - field_values: HashMap::new(), + field_values: BTreeMap::new(), deposits: vec![], }) } @@ -160,6 +169,42 @@ impl DotrainOrderGui { pub fn get_all_field_definitions(&self) -> Vec { self.deployment.fields.clone() } + + #[wasm_bindgen(js_name = "serializeState")] + pub fn serialize(&self) -> Result { + let state = SerializedGuiState { + field_values: self.field_values.clone(), + deposits: self.deposits.clone(), + }; + let json = serde_json::to_string(&state)?; + + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(json.as_bytes())?; + let compressed = encoder.finish()?; + + Ok(URL_SAFE.encode(compressed)) + } + + #[wasm_bindgen(js_name = "deserializeState")] + pub fn deserialize_state(&mut self, serialized: String) -> Result<(), GuiError> { + let compressed = URL_SAFE.decode(serialized)?; + + let mut decoder = GzDecoder::new(&compressed[..]); + let mut json = String::new(); + decoder.read_to_string(&mut json)?; + + let state: SerializedGuiState = serde_json::from_str(&json)?; + self.field_values = state.field_values; + self.deposits = state.deposits; + + Ok(()) + } + + #[wasm_bindgen(js_name = "clearState")] + pub fn clear_state(&mut self) { + self.field_values.clear(); + self.deposits.clear(); + } } #[derive(Error, Debug)] @@ -177,6 +222,12 @@ pub enum GuiError { #[error(transparent)] ParseGuiConfigSourceError(#[from] ParseGuiConfigSourceError), #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + SerdeJsonError(#[from] serde_json::Error), + #[error(transparent)] + Base64Error(#[from] base64::DecodeError), + #[error(transparent)] SerdeWasmBindgenError(#[from] serde_wasm_bindgen::Error), } impl From for JsValue { From a530babc2aec5f38833c85203a2e8575eb237c21 Mon Sep 17 00:00:00 2001 From: findolor Date: Tue, 12 Nov 2024 09:19:26 +0300 Subject: [PATCH 3/8] add tests --- packages/orderbook/test/js_api/gui.test.ts | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/orderbook/test/js_api/gui.test.ts b/packages/orderbook/test/js_api/gui.test.ts index b0f9aa422..51271493f 100644 --- a/packages/orderbook/test/js_api/gui.test.ts +++ b/packages/orderbook/test/js_api/gui.test.ts @@ -256,4 +256,54 @@ describe("Rain Orderbook JS API Package Bindgen Tests - Gui", async function () ); }); }); + + describe("state management tests", async () => { + let serializedString = + "H4sIAAAAAAAA_3WNwQ6CMBBE_2XPaHahLYVfMca03cU0YjEWjAnh360cvHmazEzezApDlJEvLzcukqFfwcfEMV0PBD3gm-pGadPaDp0PLMM_D9UPrAtIiLBVwPKYcpzL7mmFebpJKt2uVAB3n5Y0l0Tj0Xw981Ny3n9DTU3NqLmh0JFytg3GEAXCVlknHpXXVuwA23n7AOb4sTzEAAAA"; + let gui: DotrainOrderGui; + beforeAll(async () => { + gui = await DotrainOrderGui.init(dotrainWithGui, "some-deployment"); + + gui.saveFieldValue( + "binding-1", + "0x1234567890abcdef1234567890abcdef12345678" + ); + gui.saveFieldValue("binding-2", "100"); + gui.saveDeposit("token1", "50.6"); + }); + + it("should serialize gui state", async () => { + const serialized = gui.serializeState(); + assert.equal(serialized, serializedString); + }); + + it("should deserialize gui state", async () => { + gui.deserializeState(serializedString); + const fieldValues = gui.getAllFieldValues(); + assert.equal(fieldValues.length, 2); + assert.equal(fieldValues[0].binding, "binding-1"); + assert.equal( + fieldValues[0].value, + "0x1234567890abcdef1234567890abcdef12345678" + ); + assert.equal(fieldValues[1].binding, "binding-2"); + assert.equal(fieldValues[1].value, "100"); + const deposits = gui.getDeposits(); + assert.equal(deposits.length, 1); + assert.equal(deposits[0].token, "token1"); + assert.equal(deposits[0].amount, "50.6"); + assert.equal( + deposits[0].address, + "0xc2132d05d31c914a87c6611c10748aeb04b58e8f" + ); + }); + + it("should clear state", async () => { + gui.clearState(); + const fieldValues = gui.getAllFieldValues(); + assert.equal(fieldValues.length, 0); + const deposits = gui.getDeposits(); + assert.equal(deposits.length, 0); + }); + }); }); From 3a5a6b9bf7fc2987498880c8e941e9fc95835183 Mon Sep 17 00:00:00 2001 From: findolor Date: Tue, 12 Nov 2024 11:13:09 +0300 Subject: [PATCH 4/8] add clear state before deserializing --- packages/orderbook/test/js_api/gui.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/orderbook/test/js_api/gui.test.ts b/packages/orderbook/test/js_api/gui.test.ts index 51271493f..58fdf197c 100644 --- a/packages/orderbook/test/js_api/gui.test.ts +++ b/packages/orderbook/test/js_api/gui.test.ts @@ -278,6 +278,7 @@ describe("Rain Orderbook JS API Package Bindgen Tests - Gui", async function () }); it("should deserialize gui state", async () => { + gui.clearState(); gui.deserializeState(serializedString); const fieldValues = gui.getAllFieldValues(); assert.equal(fieldValues.length, 2); From 41dbdae11d659c98049c54198213ec92ee7b2bf3 Mon Sep 17 00:00:00 2001 From: findolor Date: Tue, 12 Nov 2024 19:47:53 +0300 Subject: [PATCH 5/8] add dependencies --- Cargo.lock | 12 +++++++++++- crates/js_api/Cargo.toml | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8230c8fb..e58cdecdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1717,6 +1717,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -6440,6 +6449,7 @@ version = "0.0.0-alpha.0" dependencies = [ "alloy", "base64 0.22.1", + "bincode", "cynic", "flate2", "js-sys", @@ -6450,7 +6460,7 @@ dependencies = [ "reqwest 0.12.5", "serde", "serde-wasm-bindgen 0.6.5", - "serde_json", + "sha2", "thiserror", "tokio", "tsify", diff --git a/crates/js_api/Cargo.toml b/crates/js_api/Cargo.toml index f8ec46b78..56c9e5539 100644 --- a/crates/js_api/Cargo.toml +++ b/crates/js_api/Cargo.toml @@ -28,4 +28,5 @@ tokio = { workspace = true, features = ["sync", "macros", "io-util", "rt", "time alloy = { workspace = true, features = [ "dyn-abi" ] } flate2 = "1.0.34" base64 = "0.22.1" -serde_json = "1.0.132" \ No newline at end of file +bincode = "1.3.3" +sha2 = "0.10.8" \ No newline at end of file From 2444d58a4363ba34bd62a0b0aaaedb304648f04e Mon Sep 17 00:00:00 2001 From: findolor Date: Tue, 12 Nov 2024 19:48:17 +0300 Subject: [PATCH 6/8] update serialization function and add config hash check --- crates/js_api/src/gui/mod.rs | 40 ++++------------ crates/js_api/src/gui/state_management.rs | 56 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 31 deletions(-) create mode 100644 crates/js_api/src/gui/state_management.rs diff --git a/crates/js_api/src/gui/mod.rs b/crates/js_api/src/gui/mod.rs index 80690eb16..49f846899 100644 --- a/crates/js_api/src/gui/mod.rs +++ b/crates/js_api/src/gui/mod.rs @@ -21,6 +21,9 @@ use wasm_bindgen::{ prelude::*, }; +mod state_management; +use state_management::{clear_state, deserialize_state, serialize}; + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct TokenDeposit { @@ -39,12 +42,6 @@ pub struct FieldValuePair { } impl_wasm_traits!(FieldValuePair); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -struct SerializedGuiState { - field_values: BTreeMap, - deposits: Vec, -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[wasm_bindgen] pub struct DotrainOrderGui { @@ -172,38 +169,17 @@ impl DotrainOrderGui { #[wasm_bindgen(js_name = "serializeState")] pub fn serialize(&self) -> Result { - let state = SerializedGuiState { - field_values: self.field_values.clone(), - deposits: self.deposits.clone(), - }; - let json = serde_json::to_string(&state)?; - - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); - encoder.write_all(json.as_bytes())?; - let compressed = encoder.finish()?; - - Ok(URL_SAFE.encode(compressed)) + serialize(self) } #[wasm_bindgen(js_name = "deserializeState")] pub fn deserialize_state(&mut self, serialized: String) -> Result<(), GuiError> { - let compressed = URL_SAFE.decode(serialized)?; - - let mut decoder = GzDecoder::new(&compressed[..]); - let mut json = String::new(); - decoder.read_to_string(&mut json)?; - - let state: SerializedGuiState = serde_json::from_str(&json)?; - self.field_values = state.field_values; - self.deposits = state.deposits; - - Ok(()) + deserialize_state(self, serialized) } #[wasm_bindgen(js_name = "clearState")] pub fn clear_state(&mut self) { - self.field_values.clear(); - self.deposits.clear(); + clear_state(self) } } @@ -217,6 +193,8 @@ pub enum GuiError { FieldBindingNotFound(String), #[error("Deposit token not found in gui config: {0}")] DepositTokenNotFound(String), + #[error("Deserialized config mismatch")] + DeserializedConfigMismatch, #[error(transparent)] DotrainOrderError(#[from] DotrainOrderError), #[error(transparent)] @@ -224,7 +202,7 @@ pub enum GuiError { #[error(transparent)] IoError(#[from] std::io::Error), #[error(transparent)] - SerdeJsonError(#[from] serde_json::Error), + BincodeError(#[from] bincode::Error), #[error(transparent)] Base64Error(#[from] base64::DecodeError), #[error(transparent)] diff --git a/crates/js_api/src/gui/state_management.rs b/crates/js_api/src/gui/state_management.rs new file mode 100644 index 000000000..5ab325d93 --- /dev/null +++ b/crates/js_api/src/gui/state_management.rs @@ -0,0 +1,56 @@ +use super::*; +use sha2::{Digest, Sha256}; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +struct SerializedGuiState { + config_hash: String, + field_values: BTreeMap, + deposits: Vec, +} + +fn compute_config_hash(gui: &DotrainOrderGui) -> String { + let config = gui.get_gui_config(); + let bytes = bincode::serialize(&config).expect("Failed to serialize config"); + let hash = Sha256::digest(&bytes); + format!("{:x}", hash) +} + +pub fn serialize(gui: &DotrainOrderGui) -> Result { + let config_hash = compute_config_hash(gui); + + let state = SerializedGuiState { + config_hash, + field_values: gui.field_values.clone(), + deposits: gui.deposits.clone(), + }; + let bytes = bincode::serialize(&state)?; + + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&bytes)?; + let compressed = encoder.finish()?; + + Ok(URL_SAFE.encode(compressed)) +} + +pub fn deserialize_state(gui: &mut DotrainOrderGui, serialized: String) -> Result<(), 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 state: SerializedGuiState = bincode::deserialize(&bytes)?; + gui.field_values = state.field_values; + gui.deposits = state.deposits; + + if state.config_hash != compute_config_hash(gui) { + return Err(GuiError::DeserializedConfigMismatch); + } + + Ok(()) +} + +pub fn clear_state(gui: &mut DotrainOrderGui) { + gui.field_values.clear(); + gui.deposits.clear(); +} From 21f849c93a0eea29409e517149f44e0f55c5f37c Mon Sep 17 00:00:00 2001 From: findolor Date: Tue, 12 Nov 2024 19:48:21 +0300 Subject: [PATCH 7/8] update tests --- packages/orderbook/test/js_api/gui.test.ts | 42 +++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/orderbook/test/js_api/gui.test.ts b/packages/orderbook/test/js_api/gui.test.ts index 58fdf197c..c7515e128 100644 --- a/packages/orderbook/test/js_api/gui.test.ts +++ b/packages/orderbook/test/js_api/gui.test.ts @@ -40,6 +40,30 @@ gui: - value: "582.1" - value: "648.239" `; +const guiConfig2 = ` +gui: + name: Test test + description: Test test test + deployments: + - deployment: other-deployment + name: Test test + description: Test test test + deposits: + - token: token1 + min: 0 + presets: + - "0" + - token: token2 + min: 0 + presets: + - "0" + fields: + - binding: test-binding + name: Test binding + description: Test binding description + presets: + - value: "test-value" +`; const dotrain = ` networks: @@ -97,6 +121,9 @@ deployments: some-deployment: scenario: some-scenario order: some-order + other-deployment: + scenario: some-scenario + order: some-order --- #calculate-io _ _: 0 0; @@ -259,7 +286,7 @@ describe("Rain Orderbook JS API Package Bindgen Tests - Gui", async function () describe("state management tests", async () => { let serializedString = - "H4sIAAAAAAAA_3WNwQ6CMBBE_2XPaHahLYVfMca03cU0YjEWjAnh360cvHmazEzezApDlJEvLzcukqFfwcfEMV0PBD3gm-pGadPaDp0PLMM_D9UPrAtIiLBVwPKYcpzL7mmFebpJKt2uVAB3n5Y0l0Tj0Xw981Ny3n9DTU3NqLmh0JFytg3GEAXCVlknHpXXVuwA23n7AOb4sTzEAAAA"; + "H4sIAAAAAAAA_3WMuw3CQBBE-RgkMiQIXQGS0e79fM6I6eLu9hZZSCZxQAdIBCCKoQECGqAMmiBgIyQmmZkXvM3gG3IeCUxWDROSy16TjTpapSKj14lTysaAR4bIpG1tgvG1TxY4MDY0Es9MOrYdtd2uwpUAOKLSxrraNxBiosz__q9CjQUgwFDmVLo_7HOHhTwLa7eU_VhUk1d5256eoZz353dxv1w_3kRs8-0AAAA="; let gui: DotrainOrderGui; beforeAll(async () => { gui = await DotrainOrderGui.init(dotrainWithGui, "some-deployment"); @@ -299,6 +326,19 @@ describe("Rain Orderbook JS API Package Bindgen Tests - Gui", async function () ); }); + it("should throw error during deserialize if config is different", async () => { + let dotrain2 = ` +${guiConfig2} + +${dotrain} +`; + let gui2 = await DotrainOrderGui.init(dotrain2, "other-deployment"); + let serialized = gui2.serializeState(); + expect(() => gui.deserializeState(serialized)).toThrow( + "Deserialized config mismatch" + ); + }); + it("should clear state", async () => { gui.clearState(); const fieldValues = gui.getAllFieldValues(); From 80a8c16af7ff09c2e9313eb5665fc6d1971eee05 Mon Sep 17 00:00:00 2001 From: findolor Date: Wed, 13 Nov 2024 10:30:22 +0300 Subject: [PATCH 8/8] refactor DotrainOrderGui implementations --- crates/js_api/src/gui/mod.rs | 16 ----- crates/js_api/src/gui/state_management.rs | 76 ++++++++++++----------- 2 files changed, 41 insertions(+), 51 deletions(-) diff --git a/crates/js_api/src/gui/mod.rs b/crates/js_api/src/gui/mod.rs index 49f846899..06533f362 100644 --- a/crates/js_api/src/gui/mod.rs +++ b/crates/js_api/src/gui/mod.rs @@ -22,7 +22,6 @@ use wasm_bindgen::{ }; mod state_management; -use state_management::{clear_state, deserialize_state, serialize}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] @@ -166,21 +165,6 @@ impl DotrainOrderGui { pub fn get_all_field_definitions(&self) -> Vec { self.deployment.fields.clone() } - - #[wasm_bindgen(js_name = "serializeState")] - pub fn serialize(&self) -> Result { - serialize(self) - } - - #[wasm_bindgen(js_name = "deserializeState")] - pub fn deserialize_state(&mut self, serialized: String) -> Result<(), GuiError> { - deserialize_state(self, serialized) - } - - #[wasm_bindgen(js_name = "clearState")] - pub fn clear_state(&mut self) { - clear_state(self) - } } #[derive(Error, Debug)] diff --git a/crates/js_api/src/gui/state_management.rs b/crates/js_api/src/gui/state_management.rs index 5ab325d93..5cd50e817 100644 --- a/crates/js_api/src/gui/state_management.rs +++ b/crates/js_api/src/gui/state_management.rs @@ -8,49 +8,55 @@ struct SerializedGuiState { deposits: Vec, } -fn compute_config_hash(gui: &DotrainOrderGui) -> String { - let config = gui.get_gui_config(); - let bytes = bincode::serialize(&config).expect("Failed to serialize config"); - let hash = Sha256::digest(&bytes); - format!("{:x}", hash) -} +#[wasm_bindgen] +impl DotrainOrderGui { + fn compute_config_hash(&self) -> String { + let config = self.get_gui_config(); + let bytes = bincode::serialize(&config).expect("Failed to serialize config"); + let hash = Sha256::digest(&bytes); + format!("{:x}", hash) + } -pub fn serialize(gui: &DotrainOrderGui) -> Result { - let config_hash = compute_config_hash(gui); + #[wasm_bindgen(js_name = "serializeState")] + pub fn serialize(&self) -> Result { + let config_hash = self.compute_config_hash(); - let state = SerializedGuiState { - config_hash, - field_values: gui.field_values.clone(), - deposits: gui.deposits.clone(), - }; - let bytes = bincode::serialize(&state)?; + let state = SerializedGuiState { + config_hash, + field_values: self.field_values.clone(), + deposits: self.deposits.clone(), + }; + let bytes = bincode::serialize(&state)?; - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); - encoder.write_all(&bytes)?; - let compressed = encoder.finish()?; + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&bytes)?; + let compressed = encoder.finish()?; - Ok(URL_SAFE.encode(compressed)) -} + Ok(URL_SAFE.encode(compressed)) + } -pub fn deserialize_state(gui: &mut DotrainOrderGui, serialized: String) -> Result<(), GuiError> { - let compressed = URL_SAFE.decode(serialized)?; + #[wasm_bindgen(js_name = "deserializeState")] + pub fn deserialize_state(&mut self, serialized: String) -> Result<(), 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 mut decoder = GzDecoder::new(&compressed[..]); + let mut bytes = Vec::new(); + decoder.read_to_end(&mut bytes)?; - let state: SerializedGuiState = bincode::deserialize(&bytes)?; - gui.field_values = state.field_values; - gui.deposits = state.deposits; + let state: SerializedGuiState = bincode::deserialize(&bytes)?; + self.field_values = state.field_values; + self.deposits = state.deposits; - if state.config_hash != compute_config_hash(gui) { - return Err(GuiError::DeserializedConfigMismatch); - } + if state.config_hash != self.compute_config_hash() { + return Err(GuiError::DeserializedConfigMismatch); + } - Ok(()) -} + Ok(()) + } -pub fn clear_state(gui: &mut DotrainOrderGui) { - gui.field_values.clear(); - gui.deposits.clear(); + #[wasm_bindgen(js_name = "clearState")] + pub fn clear_state(&mut self) { + self.field_values.clear(); + self.deposits.clear(); + } }