From 4a8f2cdfbf7ff4ecd97321e6884f3bf6b83056fc Mon Sep 17 00:00:00 2001 From: aspect Date: Tue, 16 Jul 2024 19:10:02 +0300 Subject: [PATCH] hex view for ScriptBuilder (#67) --- Cargo.lock | 3 + Cargo.toml | 5 +- crypto/txscript/Cargo.toml | 1 + crypto/txscript/src/script_builder.rs | 11 ++ crypto/txscript/src/wasm/builder.rs | 10 ++ wasm/core/Cargo.toml | 2 + wasm/core/src/hex.rs | 152 ++++++++++++++++++++++++++ wasm/core/src/lib.rs | 3 +- wasm/core/src/types.rs | 2 +- 9 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 wasm/core/src/hex.rs diff --git a/Cargo.lock b/Cargo.lock index dcaef44db..6fbf15dc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3377,6 +3377,7 @@ dependencies = [ "cfg-if 1.0.0", "criterion", "hex", + "hexplay", "indexmap 2.2.6", "itertools 0.11.0", "kaspa-addresses", @@ -3675,8 +3676,10 @@ name = "kaspa-wasm-core" version = "0.14.1" dependencies = [ "faster-hex 0.6.1", + "hexplay", "js-sys", "wasm-bindgen", + "workflow-wasm", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ee054e4f2..ec54a70d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -178,14 +178,13 @@ faster-hex = "0.6.1" # TODO "0.8.1" - fails unit tests fixedstr = { version = "0.5.4", features = ["serde"] } flate2 = "1.0.28" futures = { version = "0.3.29" } -futures-util = { version = "0.3.29", default-features = false, features = [ - "alloc", -] } +futures-util = { version = "0.3.29", default-features = false, features = ["alloc"] } getrandom = { version = "0.2.10", features = ["js"] } h2 = "0.3.21" heapless = "0.7.16" hex = { version = "0.4.3", features = ["serde"] } hex-literal = "0.4.1" +hexplay = "0.3.0" hmac = { version = "0.12.1", default-features = false } home = "0.5.5" igd-next = { version = "0.14.2", features = ["aio_tokio"] } diff --git a/crypto/txscript/Cargo.toml b/crypto/txscript/Cargo.toml index c7f352e1b..dace708fd 100644 --- a/crypto/txscript/Cargo.toml +++ b/crypto/txscript/Cargo.toml @@ -16,6 +16,7 @@ wasm32-sdk = [] blake2b_simd.workspace = true borsh.workspace = true cfg-if.workspace = true +hexplay.workspace = true indexmap.workspace = true itertools.workspace = true kaspa-addresses.workspace = true diff --git a/crypto/txscript/src/script_builder.rs b/crypto/txscript/src/script_builder.rs index f1d7d716a..731c47680 100644 --- a/crypto/txscript/src/script_builder.rs +++ b/crypto/txscript/src/script_builder.rs @@ -5,6 +5,7 @@ use crate::{ opcodes::{codes::*, OP_1_NEGATE_VAL, OP_DATA_MAX_VAL, OP_DATA_MIN_VAL, OP_SMALL_INT_MAX_VAL}, MAX_SCRIPTS_SIZE, MAX_SCRIPT_ELEMENT_SIZE, }; +use hexplay::{HexView, HexViewBuilder}; use thiserror::Error; /// DEFAULT_SCRIPT_ALLOC is the default size used for the backing array @@ -248,6 +249,16 @@ impl ScriptBuilder { let trimmed = &buffer[0..trimmed_size]; self.add_data(trimmed) } + + /// Return [`HexViewBuilder`] for the script + pub fn hex_view_builder(&self) -> HexViewBuilder<'_> { + HexViewBuilder::new(&self.script) + } + + /// Return ready to use [`HexView`] for the script + pub fn hex_view(&self, offset: usize, width: usize) -> HexView<'_> { + HexViewBuilder::new(&self.script).address_offset(offset).row_width(width).finish() + } } impl Default for ScriptBuilder { diff --git a/crypto/txscript/src/wasm/builder.rs b/crypto/txscript/src/wasm/builder.rs index ce5d3ba3c..8a6688fd9 100644 --- a/crypto/txscript/src/wasm/builder.rs +++ b/crypto/txscript/src/wasm/builder.rs @@ -2,6 +2,7 @@ use crate::result::Result; use crate::{script_builder as native, standard}; use kaspa_consensus_core::tx::ScriptPublicKey; use kaspa_utils::hex::ToHex; +use kaspa_wasm_core::hex::{HexViewConfig, HexViewConfigT}; use kaspa_wasm_core::types::{BinaryT, HexString}; use std::cell::{Ref, RefCell, RefMut}; use std::rc::Rc; @@ -168,4 +169,13 @@ impl ScriptBuilder { Ok(generated_script.to_hex().into()) } + + #[wasm_bindgen(js_name = "hexView")] + pub fn hex_view(&self, args: Option) -> Result { + let inner = self.inner(); + let script = inner.script(); + + let config = args.map(HexViewConfig::try_from).transpose()?.unwrap_or_default(); + Ok(config.build(script).to_string()) + } } diff --git a/wasm/core/Cargo.toml b/wasm/core/Cargo.toml index 7ecda22a9..4c765e9bd 100644 --- a/wasm/core/Cargo.toml +++ b/wasm/core/Cargo.toml @@ -15,6 +15,8 @@ wasm32-sdk = [] wasm-bindgen.workspace = true js-sys.workspace = true faster-hex.workspace = true +hexplay.workspace = true +workflow-wasm.workspace = true [lints] workspace = true diff --git a/wasm/core/src/hex.rs b/wasm/core/src/hex.rs new file mode 100644 index 000000000..8c187e03f --- /dev/null +++ b/wasm/core/src/hex.rs @@ -0,0 +1,152 @@ +//! +//! Hex module provides a way to display binary data in a human-readable format. +//! + +use hexplay::{ + color::{Color, Spec}, + HexView, HexViewBuilder, +}; +use std::ops::Range; +use std::str::FromStr; +use wasm_bindgen::prelude::*; +use workflow_wasm::prelude::*; + +type Result = std::result::Result; + +#[derive(Default)] +pub struct HexViewConfig { + pub offset: Option, + pub replace_char: Option, + pub width: Option, + pub colors: Option)>>, +} + +impl HexViewConfig { + pub fn build(self, slice: &[u8]) -> HexView<'_> { + let mut builder = HexViewBuilder::new(slice); + + if let Some(offset) = self.offset { + builder = builder.address_offset(offset); + } + + if let Some(replace_char) = self.replace_char { + builder = builder.replacement_character(replace_char); + } + + if let Some(width) = self.width { + builder = builder.row_width(width); + } + + if let Some(colors) = self.colors { + if !colors.is_empty() { + builder = builder.add_colors(colors); + } + } + + builder.finish() + } +} + +pub struct ColorRange { + pub color: Option, + pub background: Option, + pub range: Range, +} + +impl ColorRange { + fn new(color: Option, background: Option, range: Range) -> Self { + Self { color, background, range } + } + + fn into_tuple(self) -> (Spec, Range) { + let mut spec = Spec::new(); + spec.set_fg(self.color); + spec.set_bg(self.background); + + (spec, self.range) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_HEX_VIEW: &'static str = r#" +/** + * Color range configuration for Hex View. + * + * @category General + */ +export interface IHexViewColor { + start: number; + end: number; + color?: string; + background?: string; +} + +/** + * Configuration interface for Hex View. + * + * @category General + */ +export interface IHexViewConfig { + offset? : number; + replacementCharacter? : string; + width? : number; + colors? : IHexViewColor[]; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IHexViewColor")] + pub type HexViewColorT; + #[wasm_bindgen(extends = js_sys::Array, typescript_type = "IHexViewColor[]")] + pub type HexViewColorArrayT; + #[wasm_bindgen(typescript_type = "IHexViewConfig")] + pub type HexViewConfigT; +} + +impl TryFrom for ColorRange { + type Error = JsValue; + fn try_from(js_value: JsValue) -> Result { + if let Some(object) = js_sys::Object::try_from(&js_value) { + let start = object.get_u32("start")? as usize; + let end = object.get_u32("end")? as usize; + + let color = object.get_string("color").ok(); + let color = + color.map(|color| Color::from_str(color.as_str()).map_err(|e| JsValue::from_str(&e.to_string()))).transpose()?; + + let background = object.get_string("background").ok(); + let background = background + .map(|background| Color::from_str(background.as_str()).map_err(|e| JsValue::from_str(&e.to_string()))) + .transpose()?; + + Ok(ColorRange::new(color, background, start..end)) + } else { + Err(JsValue::from_str("color range must be an object")) + } + } +} + +pub fn try_to_color_vec(js_value: JsValue) -> Result)>> { + if js_value.is_array() { + let list = js_sys::Array::from(&js_value).iter().map(TryFrom::try_from).collect::>>()?; + Ok(list.into_iter().map(ColorRange::into_tuple).collect::>()) + } else { + let tuple = ColorRange::try_from(js_value).map(ColorRange::into_tuple)?; + Ok(vec![tuple]) + } +} + +impl TryFrom for HexViewConfig { + type Error = JsValue; + fn try_from(js_value: HexViewConfigT) -> Result { + let object = js_sys::Object::try_from(&js_value).ok_or_else(|| JsValue::from_str("HexView config must be an object"))?; + + let offset = object.get_u32("offset").ok().map(|v| v as usize); + let replace_char = object.get_string("replacementCharacter").ok().map(|s| s.chars().next().unwrap_or(' ')); + let width = object.get_u32("width").ok().map(|v| v as usize); + let colors = object.get_value("colors").ok().map(try_to_color_vec).transpose()?; + + Ok(HexViewConfig { offset, replace_char, width, colors }) + } +} diff --git a/wasm/core/src/lib.rs b/wasm/core/src/lib.rs index 1710c11fe..d99f29731 100644 --- a/wasm/core/src/lib.rs +++ b/wasm/core/src/lib.rs @@ -1,4 +1,3 @@ pub mod events; +pub mod hex; pub mod types; - -// pub use types::*; diff --git a/wasm/core/src/types.rs b/wasm/core/src/types.rs index fc898ee8b..7e8e29335 100644 --- a/wasm/core/src/types.rs +++ b/wasm/core/src/types.rs @@ -30,7 +30,7 @@ impl From for HexString { impl TryFrom for String { type Error = &'static str; - fn try_from(value: HexString) -> Result { + fn try_from(value: HexString) -> std::result::Result { value.as_string().ok_or("Supplied value is not a string") } }