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
88 changes: 73 additions & 15 deletions debugger/cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SilverScript CLI Debugger

A light-weight tool for stepping through and testing SilverScript smart contracts.
A light-weight, GDB-like attempt at stepping through and testing SilverScript contracts.

### Quick Start

Expand All @@ -13,6 +13,13 @@ cli-debugger <path> -f <function> [--ctor-arg <val>]... [--arg <val>]...
cli-debugger ./counter.sil -f check --ctor-arg 10 --arg 7
```

Structured `State` and custom `struct` args use JSON:

```bash
cli-debugger ./vault.sil -f inspect --arg '{"amount":7,"tag":"0xbeef"}'
cli-debugger ./vault.sil -f inspect_many --arg '[{"amount":7},{"amount":9}]'
```

---

## Interactive Debugging
Expand Down Expand Up @@ -46,7 +53,7 @@ Stepping through 42 bytes of script
(sdb) vars
Contract Constants:
threshold (int) = 10
Entrypoint Parameters:
Call Arguments:
value (int) = 7
Locals:
doubled (int) = 14
Expand All @@ -56,20 +63,50 @@ doubled + 1 = (int) 15
Done.
```

### Essential Commands
### Commands

| Command | Action |
|---|---|
| `n` | **Next**: Step over to the next statement |
| `s` | **Step**: Step into a function |
| `c` | **Continue**: Run until the next breakpoint or completion |
| `b <line>` | **Break**: Set a breakpoint (e.g., `b 10`) |
| `n` (`next`, `over`) | **Next**: Step over to the next statement |
| `s` (`step`, `into`) | **Step**: Step into a function |
| `si` | **Step Opcode**: Advance by one VM opcode |
| `finish` (`out`) | **Step Out**: Continue until the current frame returns |
| `c` (`continue`) | **Continue**: Run until the next breakpoint or completion |
| `b [line]` (`break [line]`) | **Break**: Set a breakpoint (e.g. `b 10`) or list current breakpoints |
| `vars` | **Variables**: List all variables and constants in scope |
| `eval <expr>` | **Evaluate**: Run an expression in the current debugger scope |
| `p <name>` | **Print**: Show the value of a specific variable |
| `e <expr>` (`eval <expr>`) | **Evaluate**: Run an expression in the current debugger scope |
| `p <name>` (`print <name>`) | **Print**: Show the value of a specific variable |
| `stack` | **Stack**: Inspect the raw Kaspa VM execution stack |
| `l` | **List**: Show the source code around your current position |
| `q` | **Quit**: Exit the debugger |
| `l` (`list`) | **List**: Show the source code around your current position |
| `h` / `?` (`help`) | **Help**: Show the command summary |
| `q` (`quit`) | **Quit**: Exit the debugger |

### Inspection

Use `vars` to inspect the current source-level scope and `eval` to check expressions in that scope. This works for scalars, `State`, `State[]`, and custom `struct` values.

```bash
cli-debugger examples/debug_struct_state_matrix.sil --function inspect_state \
--ctor-arg '{"amount":3,"code":"0x1234"}' \
--arg '{"amount":5,"active":true,"tag":"0xaa"}'
```

```text
(sdb) vars
Contract Constants:
seed_pair (Pair) = {amount: 3, code: 0x1234}
Contract State:
amount (int) = 1
active (bool) = true
tag (byte[1]) = 0xaa
Call Arguments:
next_state (State) = {amount: 5, active: true, tag: 0xaa}

(sdb) eval next_state.amount + amount
next_state.amount + amount = (int) 6
```

If the contract executes a source-level `console.log(...)`, its output appears under `Console:` while stepping. The same `vars` and `eval` flow also works for custom structs such as `Pair`.

---

Expand All @@ -93,17 +130,38 @@ Run `.test.json` suites non-interactively to verify logic in bulk. If you pass a

The debugger will report `PASS` if the script result matches your `expect` field (either `pass` or `fail`).

### Commands
Structured args use the same JSON object and object-array form inside `.test.json`:

```json
{
"tests": [
{
"name": "inspect_state",
"function": "inspect",
"args": [{ "amount": 7, "tag": "0xbeef" }],
"expect": "pass"
},
{
"name": "inspect_many_states",
"function": "inspect_many",
"args": [[{ "amount": 7 }, { "amount": 9 }]],
"expect": "pass"
}
]
}
```

### Test Commands

```bash
# Run all tests using the sidecar inferred from the contract path
# Run all tests using the matching `.test.json` file inferred from the contract path
cli-debugger <contract-path> --run-all

# Run a specific test case using the sidecar inferred from the contract path
# Run a specific test case using the matching `.test.json` file inferred from the contract path
cli-debugger <contract-path> --run --test-name <name>
```

Add `--test-file <path>` to either form to use an explicit test file instead of the inferred json file path
Add `--test-file <path>` to either form to use an explicit test file instead of the inferred `.test.json` path.

**Output Example:**
```text
Expand Down
13 changes: 4 additions & 9 deletions debugger/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ fn show_vars(session: &DebugSession<'_, '_>) {
print_variable_section("Contract Constants", &variables, |origin| {
matches!(origin, VariableOrigin::ConstructorArg | VariableOrigin::Constant)
});
print_variable_section("Entrypoint Parameters", &variables, |origin| origin == VariableOrigin::Param);
print_variable_section("Contract State", &variables, |origin| origin == VariableOrigin::ContractField);
print_variable_section("Call Arguments", &variables, |origin| origin == VariableOrigin::Param);
print_variable_section("Locals", &variables, |origin| origin == VariableOrigin::Local);
}
}
Expand Down Expand Up @@ -446,14 +447,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} else {
selected_name
};
let entry = compiled
.abi
.iter()
.find(|entry| entry.name == selected_name)
.ok_or_else(|| format!("function '{selected_name}' not found"))?;

let input_types = entry.inputs.iter().map(|input| input.type_name.clone()).collect::<Vec<_>>();
let typed_args = parse_call_args(&input_types, &raw_args)?;

let typed_args = parse_call_args(&compiled.ast, &selected_name, &raw_args)?;
let sigscript = compiled.build_sig_script(&selected_name, typed_args)?;

let tx = tx_scenario.unwrap_or_else(|| TestTxScenarioResolved {
Expand Down
Loading