Skip to content

Commit

Permalink
Merge pull request #988 from rainlanguage/2024-11-12-gui-serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
hardyjosh authored Nov 13, 2024
2 parents ea82353 + 80a8c16 commit 8d4d35c
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 10 deletions.
40 changes: 34 additions & 6 deletions Cargo.lock

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

6 changes: 5 additions & 1 deletion crates/js_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ 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" ] }
alloy = { workspace = true, features = [ "dyn-abi" ] }
flate2 = "1.0.34"
base64 = "0.22.1"
bincode = "1.3.3"
sha2 = "0.10.8"
19 changes: 16 additions & 3 deletions crates/js_api/src/gui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
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,
};
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::{
Expand All @@ -18,6 +21,8 @@ use wasm_bindgen::{
prelude::*,
};

mod state_management;

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct TokenDeposit {
Expand All @@ -41,7 +46,7 @@ impl_wasm_traits!(FieldValuePair);
pub struct DotrainOrderGui {
dotrain_order: DotrainOrder,
deployment: GuiDeployment,
field_values: HashMap<String, String>,
field_values: BTreeMap<String, String>,
deposits: Vec<TokenDeposit>,
}
#[wasm_bindgen]
Expand All @@ -65,7 +70,7 @@ impl DotrainOrderGui {
Ok(Self {
dotrain_order,
deployment: gui_deployment.clone(),
field_values: HashMap::new(),
field_values: BTreeMap::new(),
deposits: vec![],
})
}
Expand Down Expand Up @@ -172,11 +177,19 @@ 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)]
ParseGuiConfigSourceError(#[from] ParseGuiConfigSourceError),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
BincodeError(#[from] bincode::Error),
#[error(transparent)]
Base64Error(#[from] base64::DecodeError),
#[error(transparent)]
SerdeWasmBindgenError(#[from] serde_wasm_bindgen::Error),
}
impl From<GuiError> for JsValue {
Expand Down
62 changes: 62 additions & 0 deletions crates/js_api/src/gui/state_management.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use super::*;
use sha2::{Digest, Sha256};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
struct SerializedGuiState {
config_hash: String,
field_values: BTreeMap<String, String>,
deposits: Vec<TokenDeposit>,
}

#[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)
}

#[wasm_bindgen(js_name = "serializeState")]
pub fn serialize(&self) -> Result<String, GuiError> {
let config_hash = self.compute_config_hash();

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()?;

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 bytes = Vec::new();
decoder.read_to_end(&mut bytes)?;

let state: SerializedGuiState = bincode::deserialize(&bytes)?;
self.field_values = state.field_values;
self.deposits = state.deposits;

if state.config_hash != self.compute_config_hash() {
return Err(GuiError::DeserializedConfigMismatch);
}

Ok(())
}

#[wasm_bindgen(js_name = "clearState")]
pub fn clear_state(&mut self) {
self.field_values.clear();
self.deposits.clear();
}
}
91 changes: 91 additions & 0 deletions packages/orderbook/test/js_api/gui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -256,4 +283,68 @@ describe("Rain Orderbook JS API Package Bindgen Tests - Gui", async function ()
);
});
});

describe("state management tests", async () => {
let serializedString =
"H4sIAAAAAAAA_3WMuw3CQBBE-RgkMiQIXQGS0e79fM6I6eLu9hZZSCZxQAdIBCCKoQECGqAMmiBgIyQmmZkXvM3gG3IeCUxWDROSy16TjTpapSKj14lTysaAR4bIpG1tgvG1TxY4MDY0Es9MOrYdtd2uwpUAOKLSxrraNxBiosz__q9CjQUgwFDmVLo_7HOHhTwLa7eU_VhUk1d5256eoZz353dxv1w_3kRs8-0AAAA=";
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.clearState();
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 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();
assert.equal(fieldValues.length, 0);
const deposits = gui.getDeposits();
assert.equal(deposits.length, 0);
});
});
});

0 comments on commit 8d4d35c

Please sign in to comment.