Skip to content

Commit

Permalink
Merge pull request #1157 from rainlanguage/2025-01-14-gui-state-ser-d…
Browse files Browse the repository at this point in the history
…eser

Improving state serialization/deserialization on DotrainOrderGui
  • Loading branch information
findolor authored Jan 16, 2025
2 parents 64a27de + 3d2d5a5 commit 6c55f0d
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 26 deletions.
2 changes: 2 additions & 0 deletions crates/js_api/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ pub enum GuiError {
TokenMustBeSelected(String),
#[error("Binding has no presets: {0}")]
BindingHasNoPresets(String),
#[error("Token not in select tokens: {0}")]
TokenNotInSelectTokens(String),
#[error(transparent)]
DotrainOrderError(#[from] DotrainOrderError),
#[error(transparent)]
Expand Down
61 changes: 60 additions & 1 deletion crates/js_api/src/gui/state_management.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
use super::*;
use rain_orderbook_app_settings::token::Token;

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

#[wasm_bindgen]
impl DotrainOrderGui {
#[wasm_bindgen(js_name = "serializeState")]
pub fn serialize(&self) -> Result<String, GuiError> {
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 {
Expand Down Expand Up @@ -50,9 +55,27 @@ impl DotrainOrderGui {
deposits.insert(k.clone(), preset);
}

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

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, output) in deployment.deployment.order.outputs.iter().enumerate() {
vault_ids.insert((false, i as u8), output.vault_id.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(),
};
let bytes = bincode::serialize(&state)?;

Expand All @@ -65,6 +88,7 @@ impl DotrainOrderGui {

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

let mut decoder = GzDecoder::new(&compressed[..]);
Expand All @@ -91,6 +115,7 @@ impl DotrainOrderGui {
(k, pair_value)
})
.collect::<BTreeMap<_, _>>();

let deposits = state
.deposits
.into_iter()
Expand All @@ -113,6 +138,40 @@ impl DotrainOrderGui {
self.field_values = field_values;
self.deposits = deposits;

for (key, token) in state.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())? {
Token::remove_record_from_yaml(
self.dotrain_order.orderbook_yaml().documents.clone(),
&key,
)?;
}
Token::add_record_to_yaml(
self.dotrain_order.orderbook_yaml().documents.clone(),
&key,
&token.network.key,
&token.address.to_string(),
token.decimals.map(|d| d.to_string()).as_deref(),
token.label.map(|l| l.to_string()).as_deref(),
token.symbol.map(|s| s.to_string()).as_deref(),
)?;
}

for ((is_input, index), vault_id) in state.vault_ids {
self.dotrain_order
.dotrain_yaml()
.get_order(&deployment.deployment.order.key)
.and_then(|mut order| {
order.update_vault_id(is_input, index, vault_id.unwrap_or_default())
})?;
}

Ok(())
}

Expand Down
98 changes: 73 additions & 25 deletions packages/orderbook/test/js_api/gui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,23 +654,47 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()

describe('state management tests', async () => {
let serializedState =
'H4sIAAAAAAAA_3WNTQ5AMBCF_UW4hbWEzJQWt3AF1ZJGUgldOL6FqYXE23zz8_JeFDzKidJYZexaYUgHCDOaxkOf2hVY-s-FrGm56PoBJjkrvfzt33AWeMVEBPCFKdHtm7b4OhMih1rc8yX-NrUAAAA=';
'H4sIAAAAAAAA_7VOyw6CMBBk1WBiPHo18QeQgqEh3E38AH4AsQqhtgSKHvx5Q9xVmhBvzGV3ZvYx4HywxmpEa7xzqS6lugFqzFkN3UcmOzFDxSVHV0IFDmGBNWJ7bo2E35E51oCx8WM2o4CtvgtPCfPUTbVFrTCmTnxf6jyThW5NErM48ps697pGvughUAf0-pieNtheRwAuLNFO-wy7AChpaqcLbTZx1sMI_malbnCuX_kxzvkbrBXoaQkCAAA=';
let gui: DotrainOrderGui;
beforeAll(async () => {
mockServer
.forPost('/rpc-url')
.once()
.withBodyIncluding('0x82ad56cb')
.thenSendJsonRpcResult(
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000'
);
gui = await DotrainOrderGui.chooseDeployment(dotrainWithGui, 'some-deployment');
mockServer
.forPost('/rpc-url')
.once()
.withBodyIncluding('0x82ad56cb')
.thenSendJsonRpcResult(
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000'
);
mockServer
.forPost('/rpc-url')
.once()
.withBodyIncluding('0x82ad56cb')
.thenSendJsonRpcResult(
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000'
);
let dotrain3 = `${guiConfig3}
gui.saveFieldValue('binding-1', {
${dotrain}`;
gui = await DotrainOrderGui.chooseDeployment(dotrain3, 'other-deployment');

gui.saveFieldValue('test-binding', {
isPreset: true,
value: gui.getFieldDefinition('binding-1').presets[0].id
value: gui.getFieldDefinition('test-binding').presets[0].id
});
gui.saveFieldValue('binding-2', { isPreset: false, value: '100' });
gui.saveDeposit('token1', '50.6');
gui.saveDeposit('token2', '100');
gui.removeSelectToken('token1');
gui.removeSelectToken('token2');
await gui.saveSelectToken('token1', '0x6666666666666666666666666666666666666666');
await gui.saveSelectToken('token2', '0x3333333333333333333333333333333333333333');
gui.setVaultId(true, 0, '666');
gui.setVaultId(false, 0, '333');
});

it('should serialize gui state', async () => {
Expand All @@ -679,31 +703,55 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
});

it('should deserialize gui state', async () => {
gui.clearState();
mockServer
.forPost('/rpc-url')
.once()
.withBodyIncluding('0x82ad56cb')
.thenSendJsonRpcResult(
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000'
);
let dotrain3 = `${guiConfig3}
${dotrain}`;
gui = await DotrainOrderGui.chooseDeployment(dotrain3, 'other-deployment');
gui.removeSelectToken('token1');
gui.removeSelectToken('token2');

assert.equal(gui.getAllFieldValues().length, 0);
assert.equal(gui.getDeposits().length, 0);
assert.equal(gui.isSelectTokenSet('token1'), false);
assert.equal(gui.isSelectTokenSet('token2'), false);
let oldGuiDeployment: GuiDeployment = gui.getCurrentDeployment();
assert.equal(oldGuiDeployment.deployment.order.inputs[0].vaultId, '0x1');
assert.equal(oldGuiDeployment.deployment.order.outputs[0].vaultId, '0x1');

gui.deserializeState(serializedState);

const fieldValues: AllFieldValuesResult[] = gui.getAllFieldValues();
assert.equal(fieldValues.length, 2);
assert.equal(fieldValues.length, 1);
assert.deepEqual(fieldValues[0], {
binding: 'binding-1',
binding: 'test-binding',
value: {
id: '0',
name: 'Preset 1',
value: '0x1234567890abcdef1234567890abcdef12345678'
}
});
assert.deepEqual(fieldValues[1], {
binding: 'binding-2',
value: {
id: '',
name: undefined,
value: '100'
value: 'test-value'
}
});

assert.equal(gui.isSelectTokenSet('token1'), true);
assert.equal(gui.isSelectTokenSet('token2'), true);
const deposits: TokenDeposit[] = gui.getDeposits();
assert.equal(deposits.length, 1);
assert.equal(deposits.length, 2);
assert.equal(deposits[0].token, 'token1');
assert.equal(deposits[0].amount, '50.6');
assert.equal(deposits[0].address, '0xc2132d05d31c914a87c6611c10748aeb04b58e8f');
assert.equal(deposits[0].address, '0x6666666666666666666666666666666666666666');
assert.equal(deposits[1].token, 'token2');
assert.equal(deposits[1].amount, '100');
assert.equal(deposits[1].address, '0x3333333333333333333333333333333333333333');

let guiDeployment: GuiDeployment = gui.getCurrentDeployment();
assert.equal(guiDeployment.deployment.order.inputs[0].vaultId, '0x29a');
assert.equal(guiDeployment.deployment.order.outputs[0].vaultId, '0x14d');
});

it('should clear state', async () => {
Expand All @@ -715,22 +763,22 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
});

it('should check if field is preset', async () => {
gui.saveFieldValue('binding-1', {
gui.saveFieldValue('test-binding', {
isPreset: true,
value: gui.getFieldDefinition('binding-1').presets[0].id
value: gui.getFieldDefinition('test-binding').presets[0].id
});
assert.equal(gui.isFieldPreset('binding-1'), true);
gui.saveFieldValue('binding-2', {
assert.equal(gui.isFieldPreset('test-binding'), true);
gui.saveFieldValue('test-binding', {
isPreset: false,
value: '100'
});
assert.equal(gui.isFieldPreset('binding-2'), false);
assert.equal(gui.isFieldPreset('test-binding'), false);
});

it('should check if deposit is preset', async () => {
gui.saveDeposit('token1', '55');
assert.equal(gui.isDepositPreset('token1'), false);
gui.saveDeposit('token1', '100');
gui.saveDeposit('token1', '0');
assert.equal(gui.isDepositPreset('token1'), true);
});
});
Expand Down

0 comments on commit 6c55f0d

Please sign in to comment.