Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions crates/common/src/raindex_order_builder/order_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ pub struct DepositAndAddOrderCalldataResult(pub Bytes);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct IOVaultIds(pub HashMap<String, HashMap<String, Option<U256>>>);

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct IOVaultless(pub HashMap<String, HashMap<String, bool>>);

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct WithdrawCalldataResult(pub Vec<Bytes>);

Expand Down Expand Up @@ -477,6 +480,58 @@ impl RaindexOrderBuilder {
Ok(IOVaultIds(map))
}

pub fn set_vaultless(
&mut self,
r#type: VaultType,
token: String,
vaultless: bool,
) -> Result<(), RaindexOrderBuilderError> {
let deployment = self.get_current_deployment()?;
self.dotrain_order
.dotrain_yaml()
.get_order(&deployment.deployment.order.key)?
.update_vaultless(r#type, token, vaultless)?;

Ok(())
}

pub fn get_vaultless(&self) -> Result<IOVaultless, RaindexOrderBuilderError> {
let deployment = self.get_current_deployment()?;

let input_map = deployment
.deployment
.order
.inputs
.iter()
.map(|input| {
input
.token
.as_ref()
.map(|token| (token.key.clone(), input.vaultless))
.ok_or(RaindexOrderBuilderError::SelectTokensNotSet)
})
.collect::<Result<HashMap<_, _>, _>>()?;

let output_map = deployment
.deployment
.order
.outputs
.iter()
.map(|output| {
output
.token
.as_ref()
.map(|token| (token.key.clone(), output.vaultless))
.ok_or(RaindexOrderBuilderError::SelectTokensNotSet)
})
.collect::<Result<HashMap<_, _>, _>>()?;

Ok(IOVaultless(HashMap::from([
("input".to_string(), input_map),
("output".to_string(), output_map),
])))
}

pub fn has_any_vault_id(&self) -> Result<bool, RaindexOrderBuilderError> {
let map = self.get_vault_ids()?;
Ok(map
Expand Down Expand Up @@ -811,6 +866,59 @@ mod tests {
assert_eq!(res.0["output"]["token1"], Some(U256::from(888)));
}

#[tokio::test]
async fn test_set_and_get_vaultless() {
let mut builder = initialize_builder(Some("other-deployment".to_string())).await;

let res = builder.get_vaultless().unwrap();
assert_eq!(res.0.len(), 2);
assert!(!res.0["input"]["token1"]);
assert!(!res.0["output"]["token1"]);

builder
.set_vaultless(VaultType::Input, "token1".to_string(), true)
.unwrap();

let res = builder.get_vaultless().unwrap();
assert!(res.0["input"]["token1"]);
assert!(!res.0["output"]["token1"]);
assert_eq!(builder.get_vault_ids().unwrap().0["input"]["token1"], None);
assert!(builder
.generate_dotrain_text()
.unwrap()
.contains("vaultless"));

builder
.set_vault_id(
VaultType::Input,
"token1".to_string(),
Some("999".to_string()),
)
.unwrap();

let res = builder.get_vaultless().unwrap();
assert!(!res.0["input"]["token1"]);
assert_eq!(
builder.get_vault_ids().unwrap().0["input"]["token1"],
Some(U256::from(999))
);
assert!(!builder
.generate_dotrain_text()
.unwrap()
.contains("vaultless"));

builder
.set_vaultless(VaultType::Input, "token1".to_string(), true)
.unwrap();
builder
.set_vaultless(VaultType::Input, "token1".to_string(), false)
.unwrap();

let res = builder.get_vaultless().unwrap();
assert!(!res.0["input"]["token1"]);
assert_eq!(builder.get_vault_ids().unwrap().0["input"]["token1"], None);
}

#[tokio::test]
async fn test_has_any_vault_id() {
let mut builder = initialize_builder(Some("other-deployment".to_string())).await;
Expand Down
110 changes: 108 additions & 2 deletions crates/common/src/raindex_order_builder/state_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ struct SerializedBuilderState {
vault_ids: BTreeMap<(VaultType, String), Option<String>>,
dotrain_hash: String,
selected_deployment: String,
vaultless: BTreeMap<(VaultType, String), bool>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
struct LegacySerializedBuilderState {
field_values: BTreeMap<String, OrderBuilderPresetCfg>,
deposits: BTreeMap<String, OrderBuilderPresetCfg>,
select_tokens: BTreeMap<String, TokenCfg>,
vault_ids: BTreeMap<(VaultType, String), Option<String>>,
dotrain_hash: String,
selected_deployment: String,
}

impl From<LegacySerializedBuilderState> for SerializedBuilderState {
fn from(state: LegacySerializedBuilderState) -> Self {
Self {
field_values: state.field_values,
deposits: state.deposits,
select_tokens: state.select_tokens,
vault_ids: state.vault_ids,
dotrain_hash: state.dotrain_hash,
selected_deployment: state.selected_deployment,
vaultless: BTreeMap::new(),
}
}
}

impl RaindexOrderBuilder {
Expand Down Expand Up @@ -86,6 +112,22 @@ impl RaindexOrderBuilder {
Ok(vault_ids)
}

fn parse_vaultless_for_order(
documents: Vec<Arc<RwLock<StrictYaml>>>,
order_key: &str,
is_input: bool,
) -> Result<BTreeMap<(VaultType, String), bool>, RaindexOrderBuilderError> {
let r#type = if is_input {
VaultType::Input
} else {
VaultType::Output
};
Ok(OrderCfg::parse_vaultless(documents, order_key, r#type)?
.into_iter()
.map(|(token, value)| ((r#type, token), value))
.collect())
}

pub fn generate_dotrain_builder_state_instance_v1(
&self,
) -> Result<OrderBuilderStateV1, RaindexOrderBuilderError> {
Expand Down Expand Up @@ -248,11 +290,24 @@ impl RaindexOrderBuilder {
false,
)?);

let mut vaultless = BTreeMap::new();
vaultless.extend(Self::parse_vaultless_for_order(
self.dotrain_order.dotrain_yaml().documents.clone(),
&order_key,
true,
)?);
vaultless.extend(Self::parse_vaultless_for_order(
self.dotrain_order.dotrain_yaml().documents.clone(),
&order_key,
false,
)?);

let state = SerializedBuilderState {
field_values: field_values.clone(),
deposits: deposits.clone(),
select_tokens: select_tokens.clone(),
vault_ids: vault_ids.clone(),
vaultless: vaultless.clone(),
dotrain_hash: self.dotrain_hash.clone(),
selected_deployment: self.selected_deployment.clone(),
};
Expand All @@ -275,7 +330,9 @@ impl RaindexOrderBuilder {
let mut decoder = GzDecoder::new(&compressed[..]);
let mut bytes = Vec::new();
decoder.read_to_end(&mut bytes)?;
let state: SerializedBuilderState = bincode::deserialize(&bytes)?;
let state: SerializedBuilderState = bincode::deserialize(&bytes).or_else(|_| {
bincode::deserialize::<LegacySerializedBuilderState>(&bytes).map(Into::into)
})?;

let dotrain_order = DotrainOrder::create_with_profile(
dotrain.clone(),
Expand Down Expand Up @@ -341,6 +398,13 @@ impl RaindexOrderBuilder {
builder.dotrain_order.dotrain_yaml().documents,
&state.selected_deployment,
)?;
for ((r#type, token), vaultless) in state.vaultless {
builder
.dotrain_order
.dotrain_yaml()
.get_order_for_builder_deployment(&order_key, &state.selected_deployment)
.and_then(|mut order| order.update_vaultless(r#type, token, vaultless))?;
}
for ((is_input, index), vault_id) in state.vault_ids {
builder
.dotrain_order
Expand Down Expand Up @@ -399,7 +463,8 @@ mod tests {
use raindex_app_settings::{network::NetworkCfg, order::VaultType, yaml::YamlParsableHash};
use std::str::FromStr;

const SERIALIZED_STATE: &str = "H4sIAAAAAAAA_21QXWvCMBRt3NgY7EkGexrsByw0qROssIchgiIIStG-ahu0JE1Km1o__oQ_Was3FYv34Z5zc07uvUnDusYb4DKSYSRXmFomngApIXWTg-CAWBUz5AVQK85k61G3x8776h2qTMUMS6YLlXJz7wtwrXXStW2hgoVYq0x3O6TTttMkwHkqDqUDlRmZ0X1v8AG0-TvbHmsJNdEryF65w3cLPZt6ND6_pGHd4m5bWo2grovqqlOpjuv-APUp99NEFsNJO4udvD_ozcM8-s95T03Gnj_0pzHf0D1huPj7NH_BBAs0vjTFIUuE2sVM6hODT8eRygEAAA==";
const SERIALIZED_STATE: &str = "H4sIAAAAAAAA_3VQXWvCMBRN3NgY7EkGexrsByw0qROssIchgjIQHGXr69aGWZImpU1XP_6EP1mrNxWr3od7zs09ufckLbSPO8DfWEWx-iMM2bgCZJQ2RS6GA4pqZskNoNGCq865aeeVx9U9VLlOOFHclDoT9t4T4MyYtO84Uoc_cqZz0-_RXtfJ0pAUmVxVClxlbFcP_dED0Pbr13zdSLiNb6HtVx6eO_ja1h-T7Uta6BBHblm9gnkebnbduut63gvQgIkgS1U5nnbzxC2Go8F3VMTvhRjo6cQPxsFnIv7ZknJSvj3av-CSh4bshpKIp1IvEq7MZVvo1AvaAGkw2IH4AQAA";
const LEGACY_SERIALIZED_STATE_WITHOUT_VAULTLESS: &str = "H4sIAAAAAAAA_21QXWvCMBRt3NgY7EkGexrsByw0qROssIchgiIIStG-ahu0JE1Km1o__oQ_Was3FYv34Z5zc07uvUnDusYb4DKSYSRXmFomngApIXWTg-CAWBUz5AVQK85k61G3x8776h2qTMUMS6YLlXJz7wtwrXXStW2hgoVYq0x3O6TTttMkwHkqDqUDlRmZ0X1v8AG0-TvbHmsJNdEryF65w3cLPZt6ND6_pGHd4m5bWo2grovqqlOpjuv-APUp99NEFsNJO4udvD_ozcM8-s95T03Gnj_0pzHf0D1huPj7NH_BBAs0vjTFIUuE2sVM6hODT8eRygEAAA==";

fn encode_state(state: &SerializedBuilderState) -> String {
let bytes = bincode::serialize(state).unwrap();
Expand Down Expand Up @@ -546,6 +611,45 @@ mod tests {
);
}

#[tokio::test]
async fn test_new_from_legacy_state_without_vaultless() {
let builder = RaindexOrderBuilder::new_from_state(
get_yaml(),
None,
LEGACY_SERIALIZED_STATE_WITHOUT_VAULTLESS.to_string(),
)
.await
.unwrap();

let vaultless = builder.get_vaultless().unwrap().0;
assert!(!vaultless.get("input").unwrap()["token1"]);
assert!(!vaultless.get("output").unwrap()["token2"]);
}

#[tokio::test]
async fn test_serialize_state_round_trips_vaultless() {
let mut builder = initialize_builder_with_select_tokens().await;
builder
.set_vaultless(VaultType::Output, "token2".to_string(), true)
.unwrap();

let state = builder.serialize_state().unwrap();
let restored = RaindexOrderBuilder::new_from_state(get_yaml(), None, state)
.await
.unwrap();

let vaultless = restored.get_vaultless().unwrap().0;
assert!(vaultless.get("output").unwrap()["token2"]);
assert_eq!(
restored.get_vault_ids().unwrap().0.get("output").unwrap()["token2"],
None
);
assert!(restored
.generate_dotrain_text()
.unwrap()
.contains("vaultless"));
}

#[tokio::test]
async fn test_new_from_state_invalid_dotrain() {
let dotrain = r#"
Expand Down Expand Up @@ -631,6 +735,7 @@ mod tests {
deposits: BTreeMap::new(),
select_tokens: BTreeMap::from([("token1".to_string(), token)]),
vault_ids: BTreeMap::new(),
vaultless: BTreeMap::new(),
dotrain_hash: RaindexOrderBuilder::compute_state_hash(&dotrain_order).unwrap(),
selected_deployment: "select-token-deployment".to_string(),
});
Expand Down Expand Up @@ -689,6 +794,7 @@ mod tests {
deposits: BTreeMap::new(),
select_tokens: BTreeMap::from([("token3".to_string(), replacement_token.clone())]),
vault_ids: BTreeMap::new(),
vaultless: BTreeMap::new(),
dotrain_hash: RaindexOrderBuilder::compute_state_hash(&dotrain_order).unwrap(),
selected_deployment: "select-token-deployment".to_string(),
});
Expand Down
27 changes: 27 additions & 0 deletions crates/js_api/src/raindex_order_builder/order_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ pub struct IOVaultIds(
);
impl_wasm_traits!(IOVaultIds);

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)]
pub struct IOVaultless(
#[tsify(type = "Map<string, Map<string, boolean>>")] pub HashMap<String, HashMap<String, bool>>,
);
impl_wasm_traits!(IOVaultless);

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)]
pub struct WithdrawCalldataResult(#[tsify(type = "string[]")] pub Vec<Bytes>);
impl_wasm_traits!(WithdrawCalldataResult);
Expand Down Expand Up @@ -177,6 +183,27 @@ impl RaindexOrderBuilder {
Ok(self.inner.get_vault_ids()?)
}

#[wasm_export(js_name = "setVaultless", unchecked_return_type = "void")]
pub fn set_vaultless(
&mut self,
#[wasm_export(param_description = "Vault type (input or output)")] r#type: VaultType,
#[wasm_export(param_description = "Token key")] token: String,
#[wasm_export(param_description = "Whether this IO is vaultless")] vaultless: bool,
) -> Result<(), RaindexOrderBuilderWasmError> {
self.inner.set_vaultless(r#type, token, vaultless)?;
self.execute_state_update_callback()?;
Ok(())
}

#[wasm_export(
js_name = "getVaultless",
unchecked_return_type = "IOVaultless",
return_description = "Map of input/output vaultless flags by token"
)]
pub fn get_vaultless(&self) -> Result<inner_ops::IOVaultless, RaindexOrderBuilderWasmError> {
Ok(self.inner.get_vaultless()?)
}

#[wasm_export(
js_name = "hasAnyVaultId",
unchecked_return_type = "boolean",
Expand Down
Loading
Loading