Skip to content

Commit

Permalink
Merge pull request #1234 from rainlanguage/2025-02-04-bindings-fix-ra…
Browse files Browse the repository at this point in the history
…inlang-component

Add component to show rainlang and fix scenario binding update function
  • Loading branch information
findolor authored Feb 5, 2025
2 parents a08cc94 + 82a31c0 commit cc0ff8b
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 56 deletions.
15 changes: 14 additions & 1 deletion crates/js_api/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ use rain_orderbook_app_settings::{
},
network::Network,
order::Order,
yaml::YamlError,
yaml::{dotrain::DotrainYaml, YamlError, YamlParsable},
};
use rain_orderbook_bindings::{impl_all_wasm_traits, wasm_traits::prelude::*};
use rain_orderbook_common::{
dotrain::{types::patterns::FRONTMATTER_SEPARATOR, RainDocument},
dotrain_order::{calldata::DotrainOrderCalldataError, DotrainOrder, DotrainOrderError},
erc20::ERC20,
};
Expand Down Expand Up @@ -160,6 +161,18 @@ impl DotrainOrderGui {
Gui::parse_deployment_details(dotrain_order.dotrain_yaml().documents.clone())?;
Ok(DeploymentDetails(deployment_details.into_iter().collect()))
}

#[wasm_bindgen(js_name = "generateDotrainText")]
pub fn generate_dotrain_text(&self) -> Result<String, GuiError> {
let rain_document = RainDocument::create(self.dotrain_order.dotrain(), None, None, None);
let dotrain = format!(
"{}\n{}\n{}",
DotrainYaml::get_yaml_string(self.dotrain_order.dotrain_yaml().documents[0].clone(),)?,
FRONTMATTER_SEPARATOR,
rain_document.body()
);
Ok(dotrain)
}
}

#[derive(Error, Debug)]
Expand Down
16 changes: 10 additions & 6 deletions crates/js_api/src/gui/order_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,7 @@ impl DotrainOrderGui {
Ok(())
}

fn update_config_source_bindings(
&mut self,
deployment: &GuiDeployment,
) -> Result<(), GuiError> {
fn update_bindings(&mut self, deployment: &GuiDeployment) -> Result<(), GuiError> {
self.dotrain_order
.dotrain_yaml()
.get_scenario(&deployment.deployment.scenario.key)?
Expand Down Expand Up @@ -240,7 +237,7 @@ impl DotrainOrderGui {
let deployment = self.get_current_deployment()?;
self.check_select_tokens()?;
self.populate_vault_ids(&deployment)?;
self.update_config_source_bindings(&deployment)?;
self.update_bindings(&deployment)?;
let deployment = self.get_current_deployment()?;

let calldata = self
Expand All @@ -257,7 +254,7 @@ impl DotrainOrderGui {
let deployment = self.get_current_deployment()?;
self.check_select_tokens()?;
self.populate_vault_ids(&deployment)?;
self.update_config_source_bindings(&deployment)?;
self.update_bindings(&deployment)?;
let deployment = self.get_current_deployment()?;

let token_deposits = self
Expand Down Expand Up @@ -313,4 +310,11 @@ impl DotrainOrderGui {
.update_vault_id(is_input, index, vault_id)?;
Ok(())
}

#[wasm_bindgen(js_name = "updateScenarioBindings")]
pub fn update_scenario_bindings(&mut self) -> Result<(), GuiError> {
let deployment = self.get_current_deployment()?;
self.update_bindings(&deployment)?;
Ok(())
}
}
107 changes: 59 additions & 48 deletions crates/settings/src/scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::*;
use blocks::Blocks;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
num::ParseIntError,
sync::{Arc, RwLock},
};
Expand Down Expand Up @@ -199,6 +199,7 @@ impl Scenario {
let base_scenario = scenario_parts[0];

let mut new_bindings = Hash::new();
let mut unhandled_bindings: HashSet<String> = bindings.keys().cloned().collect();
for (k, v) in bindings {
new_bindings.insert(StrictYaml::String(k), StrictYaml::String(v));
}
Expand All @@ -221,68 +222,78 @@ impl Scenario {
{
let updates: Vec<_> = base_bindings
.keys()
.filter_map(|k| new_bindings.get(k).map(|v| (k.clone(), v.clone())))
.filter_map(|k| {
if let Some(v) = new_bindings.get(k) {
unhandled_bindings.remove(k.as_str().unwrap_or_default());
Some((k.clone(), v.clone()))
} else {
None
}
})
.collect();
for (k, v) in updates {
base_bindings.insert(k, v);
}
}

let scenario_parts_vec: Vec<_> =
scenario_parts.iter().skip(1).collect();
let mut current = scenario;
let scenario_parts_vec: Vec<_> = scenario_parts.iter().skip(1).collect();
let mut current = scenario;

for &part in scenario_parts_vec {
let next_scenario =
if let Some(StrictYaml::Hash(ref mut sub_scenarios)) = current
.get_mut(&StrictYaml::String("scenarios".to_string()))
for &part in scenario_parts_vec {
let next_scenario =
if let Some(StrictYaml::Hash(ref mut sub_scenarios)) =
current.get_mut(&StrictYaml::String("scenarios".to_string()))
{
if let Some(StrictYaml::Hash(ref mut sub_scenario)) =
sub_scenarios.get_mut(&StrictYaml::String(part.to_string()))
{
if let Some(StrictYaml::Hash(ref mut sub_scenario)) =
sub_scenarios
.get_mut(&StrictYaml::String(part.to_string()))
if let Some(StrictYaml::Hash(ref mut sub_bindings)) =
sub_scenario.get_mut(&StrictYaml::String(
"bindings".to_string(),
))
{
if let Some(StrictYaml::Hash(ref mut sub_bindings)) =
sub_scenario.get_mut(&StrictYaml::String(
"bindings".to_string(),
))
{
let sub_updates: Vec<_> = sub_bindings
.keys()
.filter_map(|k| {
new_bindings
.get(k)
.map(|v| (k.clone(), v.clone()))
})
.collect();

for (k, v) in sub_updates {
sub_bindings.insert(k, v);
}
} else {
return Err(YamlError::ParseError(format!(
"bindings not found in scenario {}",
part
)));
let updates: Vec<_> = sub_bindings
.keys()
.filter_map(|k| {
if let Some(v) = new_bindings.get(k) {
unhandled_bindings
.remove(k.as_str().unwrap_or_default());
Some((k.clone(), v.clone()))
} else {
None
}
})
.collect();
for (k, v) in updates {
sub_bindings.insert(k, v);
}
sub_scenario
} else {
return Err(YamlError::ParseError(format!(
"{} not found in sub scenarios",
part
)));
}
sub_scenario
} else {
return Err(YamlError::ParseError(format!(
"scenarios not found for part {}",
"{} not found in sub scenarios",
part
)));
};
current = next_scenario;
}
} else {
return Err(YamlError::ParseError(format!(
"scenarios not found for part {}",
part
)));
};
current = next_scenario;
}

if let Some(StrictYaml::Hash(ref mut lowest_bindings)) =
current.get_mut(&StrictYaml::String("bindings".to_string()))
{
for key in unhandled_bindings {
if let Some(value) =
new_bindings.get(&StrictYaml::String(key.clone()))
{
lowest_bindings.insert(StrictYaml::String(key), value.clone());
}
}
} else {
return Err(YamlError::ParseError(format!(
"bindings not found in scenario {}",
base_scenario
)));
}
} else {
return Err(YamlError::ParseError(format!(
Expand Down
17 changes: 17 additions & 0 deletions crates/settings/src/yaml/dotrain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,23 @@ mod tests {
assert_eq!(scenario.bindings.get("key1").unwrap(), "value3");
assert_eq!(scenario.bindings.get("key2").unwrap(), "value4");
}

// Adding additional bindings
{
let dotrain_yaml = DotrainYaml::new(vec![FULL_YAML.to_string()], false).unwrap();

let mut scenario = dotrain_yaml.get_scenario("scenario1.scenario2").unwrap();
let updated_scenario = scenario
.update_bindings(HashMap::from([
("key3".to_string(), "value3".to_string()),
("key4".to_string(), "value4".to_string()),
]))
.unwrap();

assert_eq!(updated_scenario.bindings.len(), 4);
assert_eq!(updated_scenario.bindings.get("key3").unwrap(), "value3");
assert_eq!(updated_scenario.bindings.get("key4").unwrap(), "value4");
}
}

#[test]
Expand Down
34 changes: 34 additions & 0 deletions packages/ui-components/src/__tests__/ComposedRainlangModal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, fireEvent, waitFor } from '@testing-library/svelte';
import ComposedRainlangModal from '../lib/components/deployment/ComposedRainlangModal.svelte';

vi.mock('svelte-codemirror-editor', async () => {
const mockCodeMirror = (await import('../lib/__mocks__/MockComponent.svelte')).default;
return { default: mockCodeMirror };
});

describe('ComposedRainlangModal', () => {
let composeRainlangMock: ReturnType<typeof vi.fn>;

beforeEach(() => {
composeRainlangMock = vi.fn(() => Promise.resolve('mocked rainlang text'));
vi.clearAllMocks();
});

it('should open modal and display rainlang text when button is clicked', async () => {
const { getByText, getByTestId } = render(ComposedRainlangModal, {
props: {
composeRainlang: composeRainlangMock,
codeMirrorStyles: {}
}
});

const button = getByText('Show Rainlang');
await fireEvent.click(button);

await waitFor(() => {
expect(composeRainlangMock).toHaveBeenCalled();
expect(getByTestId('modal')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import CodeMirror from 'svelte-codemirror-editor';
import { RainlangLR } from 'codemirror-rainlang';
import { lightCodeMirrorTheme } from '../../utils/codeMirrorThemes';
import { Button, Modal } from 'flowbite-svelte';
export let composeRainlang: () => Promise<string | undefined>;
export let codeMirrorStyles = {};
let rainlangText: string | null = null;
let open = false;
async function generateRainlang() {
const rainlang = await composeRainlang();
if (rainlang) {
rainlangText = rainlang;
open = true;
}
}
</script>

<Button size="lg" on:click={generateRainlang}>Show Rainlang</Button>

<Modal size="xl" class="bg-opacity-90 backdrop-blur-sm" bind:open data-testid="modal">
<div data-testid="modal-content">
<CodeMirror
value={rainlangText}
extensions={[RainlangLR]}
theme={lightCodeMirrorTheme}
readonly={true}
styles={{
'&': {
height: '70vh',
width: '70vw'
},
...codeMirrorStyles
}}
/>
</div>
</Modal>
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
type GuiDeployment,
type OrderIO,
type ApprovalCalldataResult,
type DepositAndAddOrderCalldataResult
type DepositAndAddOrderCalldataResult,
DotrainOrder
} from '@rainlanguage/orderbook/js_api';
import { fade } from 'svelte/transition';
import { Button } from 'flowbite-svelte';
import { getAccount, type Config } from '@wagmi/core';
import { type Writable } from 'svelte/store';
import type { AppKit } from '@reown/appkit';
import type { Hex } from 'viem';
import ComposedRainlangModal from './ComposedRainlangModal.svelte';
enum DeploymentStepErrors {
NO_GUI = 'Error loading GUI',
Expand Down Expand Up @@ -194,6 +196,16 @@
inputVaultIds = new Array(deployment.deployment.order.inputs.length).fill('');
outputVaultIds = new Array(deployment.deployment.order.outputs.length).fill('');
}
async function composeRainlang() {
if (!gui) return;
gui.updateScenarioBindings();
const deployment = gui.getCurrentDeployment();
const dotrain = gui.generateDotrainText();
const dotrainOrder = await DotrainOrder.create(dotrain);
const composedRainlang = await dotrainOrder.composeDeploymentToRainlang(deployment.key);
return composedRainlang;
}
</script>

<div>
Expand Down Expand Up @@ -280,6 +292,7 @@
{/if}
<div class="flex flex-col gap-2">
{#if $wagmiConnected}
<ComposedRainlangModal {composeRainlang} />
<Button size="lg" on:click={handleAddOrder}>Deploy Strategy</Button>
{:else}
<WalletConnect {appKitModal} connected={wagmiConnected} />
Expand Down

0 comments on commit cc0ff8b

Please sign in to comment.