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
40 changes: 40 additions & 0 deletions internal/cmd/dry_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,43 @@ func extractLedgerKeysFromEnvelope(env *xdr.TransactionEnvelope) ([]string, erro
_ = env
return []string{}, nil
}

func extractSignerCountFromEnvelope(env *xdr.TransactionEnvelope) uint32 {
var sigs int
var ops []xdr.Operation

switch env.Type {
case xdr.EnvelopeTypeEnvelopeTypeTx:
sigs = len(env.V1.Signatures)
ops = env.V1.Tx.Operations
case xdr.EnvelopeTypeEnvelopeTypeTxV0:
sigs = len(env.V0.Signatures)
ops = env.V0.Tx.Operations
case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump:
sigs = len(env.FeeBump.Signatures)
if env.FeeBump.Tx.InnerTx.Type == xdr.EnvelopeTypeEnvelopeTypeTx {
ops = env.FeeBump.Tx.InnerTx.V1.Tx.Operations
}
default:
return 1
}

// Already partially signed — use the existing count so we don't over-pad.
if sigs > 0 {
return uint32(sigs)
}

// Count InvokeHostFunction operations as a proxy for required signers.
invokeCount := 0
for _, op := range ops {
if op.Body.Type == xdr.OperationTypeInvokeHostFunction {
invokeCount++
}
}
if invokeCount > 0 {
return uint32(invokeCount)
}

// Minimum: at least one signer for any transaction.
return 1
}
2 changes: 2 additions & 0 deletions internal/decoder/suggestions.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@

// collectEvents recursively collects all events from a call tree
func (e *SuggestionEngine) collectEvents(node *CallNode) []DecodedEvent {
events := make([]DecodedEvent, 0)

if node == nil {
return nil
}
Expand All @@ -257,7 +259,7 @@
// Pre-allocate with estimated capacity to reduce re-allocations
// Estimate: node.Events + 5 events per child call
capacity := len(node.Events) + len(node.SubCalls)*5
events := make([]DecodedEvent, 0, capacity)

Check failure on line 262 in internal/decoder/suggestions.go

View workflow job for this annotation

GitHub Actions / Integration / Linux

no new variables on left side of :=

Check failure on line 262 in internal/decoder/suggestions.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Apple-Silicon

no new variables on left side of :=

Check failure on line 262 in internal/decoder/suggestions.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Intel

no new variables on left side of :=

Check failure on line 262 in internal/decoder/suggestions.go

View workflow job for this annotation

GitHub Actions / Integration / Windows

no new variables on left side of :=

events = append(events, node.Events...)

Expand Down
252 changes: 252 additions & 0 deletions internal/simulator/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// Copyright 2025 Erst Users
// SPDX-License-Identifier: Apache-2.0

package simulator

import (
"database/sql"
"os"
"path/filepath"
"time"

"github.com/dotandev/hintents/internal/authtrace"
_ "modernc.org/sqlite"
)

// SimulationRequest is the JSON object passed to the Rust binary via Stdin
type SimulationRequest struct {

Check failure on line 17 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Linux

SimulationRequest redeclared in this block

Check failure on line 17 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Apple-Silicon

SimulationRequest redeclared in this block

Check failure on line 17 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Intel

SimulationRequest redeclared in this block

Check failure on line 17 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Windows

SimulationRequest redeclared in this block
EnvelopeXdr string `json:"envelope_xdr"`
ResultMetaXdr string `json:"result_meta_xdr"`
LedgerEntries map[string]string `json:"ledger_entries,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
LedgerSequence uint32 `json:"ledger_sequence,omitempty"`
WasmPath *string `json:"wasm_path,omitempty"`
MockArgs *[]string `json:"mock_args,omitempty"`
Profile bool `json:"profile,omitempty"`
ProtocolVersion *uint32 `json:"protocol_version,omitempty"`
MockBaseFee *uint32 `json:"mock_base_fee,omitempty"`
MockGasPrice *uint64 `json:"mock_gas_price,omitempty"`

AuthTraceOpts *AuthTraceOptions `json:"auth_trace_opts,omitempty"`
CustomAuthCfg map[string]interface{} `json:"custom_auth_config,omitempty"`
ResourceCalibration *ResourceCalibration `json:"resource_calibration,omitempty"`
}

type ResourceCalibration struct {

Check failure on line 35 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Linux

ResourceCalibration redeclared in this block

Check failure on line 35 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Apple-Silicon

ResourceCalibration redeclared in this block

Check failure on line 35 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Intel

ResourceCalibration redeclared in this block

Check failure on line 35 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Windows

ResourceCalibration redeclared in this block
SHA256Fixed uint64 `json:"sha256_fixed"`
SHA256PerByte uint64 `json:"sha256_per_byte"`
Keccak256Fixed uint64 `json:"keccak256_fixed"`
Keccak256PerByte uint64 `json:"keccak256_per_byte"`
Ed25519Fixed uint64 `json:"ed25519_fixed"`
}

type AuthTraceOptions struct {

Check failure on line 43 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Linux

AuthTraceOptions redeclared in this block

Check failure on line 43 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Apple-Silicon

AuthTraceOptions redeclared in this block

Check failure on line 43 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Intel

AuthTraceOptions redeclared in this block

Check failure on line 43 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Windows

AuthTraceOptions redeclared in this block
Enabled bool `json:"enabled"`
TraceCustomContracts bool `json:"trace_custom_contracts"`
CaptureSigDetails bool `json:"capture_sig_details"`
MaxEventDepth int `json:"max_event_depth,omitempty"`
}

// DiagnosticEvent represents a structured diagnostic event from the simulator
type DiagnosticEvent struct {

Check failure on line 51 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Linux

DiagnosticEvent redeclared in this block

Check failure on line 51 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Apple-Silicon

DiagnosticEvent redeclared in this block

Check failure on line 51 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Intel

DiagnosticEvent redeclared in this block

Check failure on line 51 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Windows

DiagnosticEvent redeclared in this block
EventType string `json:"event_type"` // "contract", "system", "diagnostic"
ContractID *string `json:"contract_id,omitempty"`
Topics []string `json:"topics"`
Data string `json:"data"`
InSuccessfulContractCall bool `json:"in_successful_contract_call"`
WasmInstruction *string `json:"wasm_instruction,omitempty"`
}

// BudgetUsage represents resource consumption during simulation
type BudgetUsage struct {

Check failure on line 61 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Linux

BudgetUsage redeclared in this block

Check failure on line 61 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Apple-Silicon

BudgetUsage redeclared in this block

Check failure on line 61 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / macOS-Intel

BudgetUsage redeclared in this block

Check failure on line 61 in internal/simulator/schema.go

View workflow job for this annotation

GitHub Actions / Integration / Windows

BudgetUsage redeclared in this block
CPUInstructions uint64 `json:"cpu_instructions"`
MemoryBytes uint64 `json:"memory_bytes"`
OperationsCount int `json:"operations_count"`
CPULimit uint64 `json:"cpu_limit"`
MemoryLimit uint64 `json:"memory_limit"`
CPUUsagePercent float64 `json:"cpu_usage_percent"`
MemoryUsagePercent float64 `json:"memory_usage_percent"`
}

type SimulationResponse struct {
Status string `json:"status"` // "success" or "error"
Error string `json:"error,omitempty"`
Events []string `json:"events,omitempty"` // Raw event strings (backward compatibility)
DiagnosticEvents []DiagnosticEvent `json:"diagnostic_events,omitempty"` // Structured diagnostic events
Logs []string `json:"logs,omitempty"` // Host debug logs
Flamegraph string `json:"flamegraph,omitempty"` // SVG flamegraph
AuthTrace *authtrace.AuthTrace `json:"auth_trace,omitempty"`
BudgetUsage *BudgetUsage `json:"budget_usage,omitempty"` // Resource consumption metrics
CategorizedEvents []CategorizedEvent `json:"categorized_events,omitempty"`
ProtocolVersion *uint32 `json:"protocol_version,omitempty"` // Protocol version used
StackTrace *WasmStackTrace `json:"stack_trace,omitempty"` // Enhanced WASM stack trace on traps
SourceLocation string `json:"source_location,omitempty"`
WasmOffset *uint64 `json:"wasm_offset,omitempty"`
}

type CategorizedEvent struct {
EventType string `json:"event_type"`
ContractID *string `json:"contract_id,omitempty"`
Topics []string `json:"topics"`
Data string `json:"data"`
}

type SecurityViolation struct {
Type string `json:"type"`
Severity string `json:"severity"`
Description string `json:"description"`
Contract string `json:"contract"`
Details map[string]interface{} `json:"details,omitempty"`
}

// SourceLocation represents a precise position in Rust/WASM source code.
type SourceLocation struct {
File string `json:"file"`
Line uint `json:"line"`
Column uint `json:"column"`
ColumnEnd *uint `json:"column_end,omitempty"`
}

// Session represents a stored simulation result
type Session struct {
ID int64 `json:"id"`
TxHash string `json:"tx_hash"`
Network string `json:"network"`
Timestamp time.Time `json:"timestamp"`
Error string `json:"error,omitempty"`
Events string `json:"events,omitempty"` // JSON string
Logs string `json:"logs,omitempty"` // JSON string
}

// WasmStackTrace holds a structured WASM call stack captured on a trap.
// This bypasses Soroban Host abstractions to expose the raw Wasmi call stack.
type WasmStackTrace struct {
TrapKind interface{} `json:"trap_kind"` // Categorised trap reason
RawMessage string `json:"raw_message"` // Original error string
Frames []StackFrame `json:"frames"` // Ordered call stack frames
SorobanWrapped bool `json:"soroban_wrapped"` // Whether the error passed through Soroban Host
}

// StackFrame represents a single frame in a WASM call stack.
type StackFrame struct {
Index int `json:"index"` // Position in the call stack (0 = trap site)
FuncIndex *uint32 `json:"func_index,omitempty"` // WASM function index
FuncName *string `json:"func_name,omitempty"` // Demangled function name
WasmOffset *uint64 `json:"wasm_offset,omitempty"` // Byte offset in the WASM module
Module *string `json:"module,omitempty"` // Module name from name section
}

type DB struct {
conn *sql.DB
}

func OpenDB() (*DB, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
dbPath := filepath.Join(home, ".erst", "sessions.db")

if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
return nil, err
}

conn, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, err
}

db := &DB{conn: conn}
if err := db.init(); err != nil {
return nil, err
}

return db, nil
}

func (db *DB) init() error {
query := `
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tx_hash TEXT NOT NULL,
network TEXT NOT NULL,
timestamp DATETIME NOT NULL,
error TEXT,
events TEXT,
logs TEXT
);
CREATE INDEX IF NOT EXISTS idx_tx_hash ON sessions(tx_hash);
CREATE INDEX IF NOT EXISTS idx_error ON sessions(error);
`
_, err := db.conn.Exec(query)
return err
}

func (db *DB) SaveSession(s *Session) error {
query := "INSERT INTO sessions (tx_hash, network, timestamp, error, events, logs) VALUES (?, ?, ?, ?, ?, ?)"
_, err := db.conn.Exec(query, s.TxHash, s.Network, s.Timestamp, s.Error, s.Events, s.Logs)
return err
}

type SearchFilters struct {
Error string
Event string
Contract string
UseRegex bool
}

func (db *DB) SearchSessions(filters SearchFilters) ([]Session, error) {
query := "SELECT id, tx_hash, network, timestamp, error, events, logs FROM sessions WHERE 1=1"
var args []interface{}

if filters.Error != "" {
if filters.UseRegex {
query += " AND error REGEXP ?"
} else {
query += " AND error LIKE ?"
filters.Error = "%" + filters.Error + "%"
}
args = append(args, filters.Error)
}

if filters.Event != "" {
if filters.UseRegex {
query += " AND events REGEXP ?"
} else {
query += " AND events LIKE ?"
filters.Event = "%" + filters.Event + "%"
}
args = append(args, filters.Event)
}

if filters.Contract != "" {
if filters.UseRegex {
query += " AND (events REGEXP ? OR logs REGEXP ?)"
args = append(args, filters.Contract, filters.Contract)
} else {
query += " AND (events LIKE ? OR logs LIKE ?)"
match := "%" + filters.Contract + "%"
args = append(args, match, match)
}
}

query += " ORDER BY timestamp DESC"

rows, err := db.conn.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()

var sessions []Session
for rows.Next() {
var s Session
err := rows.Scan(&s.ID, &s.TxHash, &s.Network, &s.Timestamp, &s.Error, &s.Events, &s.Logs)
if err != nil {
return nil, err
}
sessions = append(sessions, s)
}

return sessions, nil
}
6 changes: 4 additions & 2 deletions internal/trace/search_unicode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ func TestSearchUnicode_Mixed(t *testing.T) {

nodes := []*TraceNode{
{
ID: "1",
Function: "transfer_资金",
ID: "1",
Function: "transfer_资金",

EventData: "Événement créé[OK]",
EventData: "Événement créé [DEPLOY]",
},
}
Expand Down
1 change: 1 addition & 0 deletions internal/updater/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
type Checker struct {
currentVersion string
cacheDir string
apiURL string
}

// GitHubRelease represents the GitHub API response for a release
Expand Down
Loading
Loading