Skip to content
Merged
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
1,276 changes: 1,127 additions & 149 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
[workspace]
members = ["silverscript-lang", "covenants/sdk"]
members = [
"silverscript-lang",
"debugger/session",
"debugger/cli",
"covenants/sdk",
]
exclude = ["tree-sitter"]
resolver = "2"

[workspace.package]
Expand Down
22 changes: 22 additions & 0 deletions debugger/cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "cli-debugger"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
rust-version.workspace = true

[[bin]]
name = "cli-debugger"
path = "src/main.rs"

[dependencies]
debugger-session = { path = "../session" }
silverscript-lang = { path = "../../silverscript-lang" }
kaspa-consensus-core.workspace = true
kaspa-txscript.workspace = true
kaspa-txscript-errors.workspace = true
clap = { version = "4.5.60", features = ["derive"] }
faster-hex = "0.10"
serde_json = "1.0"
39 changes: 20 additions & 19 deletions silverscript-lang/src/bin/sil-debug.rs → debugger/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync;
use kaspa_txscript::caches::Cache;
use kaspa_txscript::{EngineCtx, EngineFlags};

use silverscript_lang::ast::{Expr, parse_contract_ast};
use debugger_session::session::{DebugEngine, DebugSession};
use silverscript_lang::ast::{Expr, ExprKind, parse_contract_ast};
use silverscript_lang::compiler::{CompileOptions, compile_contract};
use silverscript_lang::debug::session::{DebugEngine, DebugSession};
use silverscript_lang::span;

const PROMPT: &str = "(sdb) ";

#[derive(Debug, Parser)]
#[command(name = "sil-debug", about = "SilverScript debugger")]
#[command(name = "cli-debugger", about = "SilverScript debugger")]
struct CliArgs {
script_path: String,
#[arg(long = "no-selector")]
Expand Down Expand Up @@ -47,26 +48,26 @@ fn parse_hex_bytes(raw: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
Ok(out)
}

fn bytes_expr(bytes: Vec<u8>) -> Expr {
Expr::Array(bytes.into_iter().map(Expr::Byte).collect())
fn bytes_expr(bytes: Vec<u8>) -> Expr<'static> {
Expr::new(ExprKind::Array(bytes.into_iter().map(Expr::byte).collect()), span::Span::default())
}

fn parse_typed_arg(type_name: &str, raw: &str) -> Result<Expr, Box<dyn std::error::Error>> {
fn parse_typed_arg(type_name: &str, raw: &str) -> Result<Expr<'static>, Box<dyn std::error::Error>> {
if let Some(element_type) = type_name.strip_suffix("[]") {
let trimmed = raw.trim();
if trimmed.starts_with('[') {
let values = serde_json::from_str::<Vec<serde_json::Value>>(trimmed)?;
let mut out = Vec::with_capacity(values.len());
for value in values {
let expr = match value {
serde_json::Value::Number(n) => Expr::Int(n.as_i64().ok_or("invalid int in array")?),
serde_json::Value::Bool(b) => Expr::Bool(b),
serde_json::Value::Number(n) => Expr::int(n.as_i64().ok_or("invalid int in array")?),
serde_json::Value::Bool(b) => Expr::bool(b),
serde_json::Value::String(s) => parse_typed_arg(element_type, &s)?,
_ => return Err("unsupported array element (expected number/bool/string)".into()),
};
out.push(expr);
}
return Ok(Expr::Array(out));
return Ok(Expr::new(ExprKind::Array(out), span::Span::default()));
}
if element_type == "byte" {
return Ok(bytes_expr(parse_hex_bytes(trimmed)?));
Expand All @@ -75,16 +76,16 @@ fn parse_typed_arg(type_name: &str, raw: &str) -> Result<Expr, Box<dyn std::erro
}

match type_name {
"int" => Ok(Expr::Int(parse_int_arg(raw)?)),
"int" => Ok(Expr::int(parse_int_arg(raw)?)),
"bool" => match raw {
"true" => Ok(Expr::Bool(true)),
"false" => Ok(Expr::Bool(false)),
"true" => Ok(Expr::bool(true)),
"false" => Ok(Expr::bool(false)),
_ => Err(format!("invalid bool '{raw}' (expected true/false)").into()),
},
"string" => Ok(Expr::String(raw.to_string())),
"string" => Ok(Expr::string(raw.to_string())),
"byte" => {
let bytes = parse_hex_bytes(raw)?;
if bytes.len() == 1 { Ok(Expr::Byte(bytes[0])) } else { Err(format!("byte expects 1 byte, got {}", bytes.len()).into()) }
if bytes.len() == 1 { Ok(Expr::byte(bytes[0])) } else { Err(format!("byte expects 1 byte, got {}", bytes.len()).into()) }
}
"bytes" => Ok(bytes_expr(parse_hex_bytes(raw)?)),
"pubkey" => {
Expand Down Expand Up @@ -126,15 +127,15 @@ fn parse_typed_arg(type_name: &str, raw: &str) -> Result<Expr, Box<dyn std::erro
}
}

fn show_stack(session: &DebugSession<'_>) {
fn show_stack(session: &DebugSession<'_, '_>) {
println!("Stack:");
let stack = session.stack();
for (i, item) in stack.iter().enumerate().rev() {
println!("[{i}] {item}");
}
}

fn show_source_context(session: &DebugSession<'_>) {
fn show_source_context(session: &DebugSession<'_, '_>) {
let Some(context) = session.source_context() else {
println!("No source context available.");
return;
Expand All @@ -146,7 +147,7 @@ fn show_source_context(session: &DebugSession<'_>) {
}
}

fn show_vars(session: &DebugSession<'_>) {
fn show_vars(session: &DebugSession<'_, '_>) {
match session.list_variables() {
Ok(variables) => {
if variables.is_empty() {
Expand All @@ -168,12 +169,12 @@ fn show_vars(session: &DebugSession<'_>) {
}
}

fn show_step_view(session: &DebugSession<'_>) {
fn show_step_view(session: &DebugSession<'_, '_>) {
show_source_context(session);
show_vars(session);
}

fn run_repl(session: &mut DebugSession<'_>) -> Result<(), kaspa_txscript_errors::TxScriptError> {
fn run_repl(session: &mut DebugSession<'_, '_>) -> Result<(), kaspa_txscript_errors::TxScriptError> {
let stdin = io::stdin();
loop {
print!("{PROMPT}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use std::process::{Command, Stdio};

fn example_contract_path() -> PathBuf {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest_dir.join("tests/examples/if_statement.sil")
manifest_dir.join("tests/if_statement.sil")
}

#[test]
fn sil_debug_repl_all_commands_smoke() {
fn cli_debugger_repl_all_commands_smoke() {
let contract_path = example_contract_path();
assert!(contract_path.exists(), "example contract not found: {}", contract_path.display());

let mut child = Command::new(env!("CARGO_BIN_EXE_sil-debug"))
let mut child = Command::new(env!("CARGO_BIN_EXE_cli-debugger"))
.arg(contract_path)
.arg("--function")
.arg("hello")
Expand All @@ -28,13 +28,13 @@ fn sil_debug_repl_all_commands_smoke() {
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn sil-debug");
.expect("failed to spawn cli-debugger");

let input = b"help\nl\nstack\nb 1\nb 7\nb\nn\nsi\nq\n";
child.stdin.as_mut().expect("stdin available").write_all(input).expect("write stdin");

let output = child.wait_with_output().expect("wait for sil-debug");
assert!(output.status.success(), "sil-debug exited with status {:?}", output.status.code());
let output = child.wait_with_output().expect("wait for cli-debugger");
assert!(output.status.success(), "cli-debugger exited with status {:?}", output.status.code());

let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
Expand Down
17 changes: 17 additions & 0 deletions debugger/cli/tests/if_statement.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pragma silverscript ^0.1.0;

contract IfStatement(int x, int y) {
entrypoint function hello(int a, int b) {
int d = a + b;
d = d - a;
if (d == x - 2) {
int c = d + b;
d = a + c;
require(c > d);
} else {
require(d == a);
}
d = d + a;
require(d == y);
}
}
24 changes: 24 additions & 0 deletions debugger/session/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "debugger-session"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
rust-version.workspace = true

[lib]
name = "debugger_session"
path = "src/lib.rs"

[dependencies]
silverscript-lang = { path = "../../silverscript-lang" }
kaspa-consensus-core.workspace = true
kaspa-txscript.workspace = true
kaspa-txscript-errors.workspace = true
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
faster-hex = "0.10"

[dev-dependencies]
kaspa-addresses.workspace = true
2 changes: 2 additions & 0 deletions debugger/session/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod presentation;
pub mod session;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::debug::SourceSpan;
use crate::debug::session::DebugValue;
use silverscript_lang::debug_info::SourceSpan;

use crate::session::DebugValue;

#[derive(Debug, Clone)]
pub struct SourceContextLine {
Expand Down
Loading