<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trident Fuzzing Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background: #0f172a;
color: #e2e8f0;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
background: #1e293b;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.4);
margin-bottom: 20px;
border: 1px solid #334155;
}
.charts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.full-width {
grid-column: 1 / -1;
}
.card {
background: #1e293b;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.4);
border: 1px solid #334155;
}
.metric {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #334155;
}
.metric:last-child {
border-bottom: none;
}
.metric-value {
font-weight: bold;
color: #38bdf8;
}
.chart-container {
position: relative;
height: 300px;
}
h1, h2 {
color: #e2e8f0;
margin-bottom: 16px;
}
h1 {
font-size: 2rem;
margin-bottom: 8px;
}
h2 {
font-size: 1.5rem;
margin-bottom: 16px;
}
.success { color: #22c55e; }
.error { color: #ef4444; }
.warning { color: #f59e0b; }
.error-section {
margin: 15px 0;
padding: 15px;
background: #334155;
border-left: 4px solid #ef4444;
border-radius: 6px;
}
.error-title {
font-weight: bold;
color: #fca5a5;
margin-bottom: 8px;
}
.error-logs {
background: #0f172a;
color: #cbd5e1;
padding: 12px;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
max-height: 800px;
overflow-y: auto;
margin-top: 8px;
border: 1px solid #475569;
line-height: 1.5;
white-space: pre-wrap;
}
.error-logs::-webkit-scrollbar {
width: 8px;
}
.error-logs::-webkit-scrollbar-track {
background: #1e293b;
border-radius: 4px;
}
.error-logs::-webkit-scrollbar-thumb {
background: #475569;
border-radius: 4px;
}
.error-logs::-webkit-scrollbar-thumb:hover {
background: #64748b;
}
.log-line {
margin: 2px 0;
}
.log-program {
color: #38bdf8;
}
.log-invoke {
color: #22c55e;
}
.log-error {
color: #f87171;
}
.log-success {
color: #22c55e;
}
.log-failed {
color: #f87171;
}
.log-message {
color: #fbbf24;
}
.log-compute {
color: #a78bfa;
}
.error-count {
font-size: 0.9em;
color: #94a3b8;
}
.collapsible {
cursor: pointer;
padding: 10px 12px;
background: #475569;
border: 1px solid #64748b;
border-radius: 6px;
margin: 8px 0;
color: #e2e8f0;
transition: background-color 0.2s;
}
.collapsible:hover {
background: #64748b;
}
.collapsible-content {
display: none;
padding: 15px;
border-top: 1px solid #64748b;
background: #334155;
border-radius: 0 0 6px 6px;
}
.summary-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: #334155;
padding: 16px;
border-radius: 8px;
text-align: center;
border: 1px solid #475569;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: #38bdf8;
}
.stat-label {
color: #94a3b8;
margin-top: 4px;
}
.footer {
margin-top: 40px;
text-align: center;
color: #64748b;
padding: 20px 0;
border-top: 1px solid #334155;
}
.info-icon {
display: inline-block;
width: 16px;
height: 16px;
background: #64748b;
color: white;
border-radius: 50%;
text-align: center;
font-size: 12px;
line-height: 16px;
margin-left: 8px;
cursor: help;
position: relative;
font-weight: bold;
}
.info-icon:hover {
background: #38bdf8;
}
.tooltip {
visibility: hidden;
opacity: 0;
position: absolute;
z-index: 1000;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background: #1e293b;
color: #e2e8f0;
padding: 12px;
border-radius: 8px;
border: 1px solid #334155;
box-shadow: 0 4px 6px rgba(0,0,0,0.4);
width: 280px;
font-size: 13px;
line-height: 1.4;
transition: opacity 0.3s, visibility 0.3s;
max-width: 90vw;
}
@media (max-width: 768px) {
.tooltip {
width: 240px;
left: -120px;
transform: none;
}
}
.tooltip::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #1e293b transparent transparent transparent;
}
.info-icon:hover .tooltip {
visibility: visible;
opacity: 1;
}
.metric-with-info {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #334155;
}
.metric-with-info:last-child {
border-bottom: none;
}
.metric-label-container {
display: flex;
align-items: center;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
gap: 20px;
margin-top: 20px;
}
.metric-card {
background: #334155;
border: 1px solid #475569;
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
}
.metric-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 2px solid #475569;
}
.metric-title {
font-size: 1.2rem;
font-weight: 600;
color: #e2e8f0;
margin: 0;
}
.metric-type-badge {
background: #38bdf8;
color: #0c4a6e;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.histogram-badge {
background: #22c55e;
color: #052e16;
}
.accumulator-badge {
background: #f59e0b;
color: #451a03;
}
.key-metrics {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
.key-metric {
text-align: center;
padding: 16px;
background: #1e293b;
border-radius: 8px;
border: 1px solid #334155;
}
.key-metric-value {
font-size: 1.8rem;
font-weight: 700;
color: #38bdf8;
margin-bottom: 4px;
word-break: break-all;
overflow-wrap: break-word;
line-height: 1.1;
}
.key-metric-label {
font-size: 0.85rem;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.detailed-metrics {
background: #1e293b;
border-radius: 8px;
padding: 16px;
border: 1px solid #334155;
}
.detailed-metrics h4 {
margin: 0 0 12px 0;
color: #cbd5e1;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.detail-metric {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #334155;
}
.detail-metric:last-child {
border-bottom: none;
}
.detail-metric-label {
display: flex;
align-items: center;
font-size: 0.9rem;
color: #cbd5e1;
}
.detail-metric-value {
font-weight: 600;
color: #e2e8f0;
font-size: 0.9rem;
}
.entropy-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-left: 8px;
}
.entropy-low { background: #ef4444; }
.entropy-medium { background: #f59e0b; }
.entropy-good { background: #22c55e; }
.entropy-excellent { background: #06b6d4; }
.accumulator-value {
text-align: center;
padding: 32px 16px;
}
.accumulator-number {
font-size: 3rem;
font-weight: 700;
color: #38bdf8;
line-height: 1;
word-break: break-all;
overflow-wrap: break-word;
}
.accumulator-label {
font-size: 0.9rem;
color: #94a3b8;
margin-top: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.no-metrics-placeholder {
text-align: center;
padding: 60px 20px;
color: #64748b;
background: #334155;
border-radius: 12px;
border: 2px dashed #475569;
}
.no-metrics-icon {
font-size: 3rem;
margin-bottom: 16px;
opacity: 0.5;
}
@media (max-width: 768px) {
.metrics-grid {
grid-template-columns: 1fr;
}
.key-metrics {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Trident Fuzzing Dashboard</h1>
<p>Fuzzing session completed. Results generated on: <span id="timestamp"></span></p>
<div id="master-seed-header" style="margin-top: 12px; padding: 12px; background: #334155; border-radius: 8px; border-left: 4px solid #38bdf8;">
<div style="font-size: 0.9rem; color: #94a3b8; margin-bottom: 4px;">Master Seed:</div>
<div id="master-seed-value" style="font-family: 'Courier New', monospace; font-size: 1rem; color: #e2e8f0; word-break: break-all; background: #1e293b; padding: 8px; border-radius: 4px; border: 1px solid #475569;">Not available</div>
</div>
</div>
<div class="summary-stats">
<div class="stat-card">
<div class="stat-value" id="total-transactions">0</div>
<div class="stat-label">Total Transactions</div>
</div>
<div class="stat-card">
<div class="stat-value success" id="total-successful">0</div>
<div class="stat-label">Successful</div>
</div>
<div class="stat-card">
<div class="stat-value error" id="total-failed">0</div>
<div class="stat-label">Failed</div>
</div>
<div class="stat-card">
<div class="stat-value warning" id="total-panicked">0</div>
<div class="stat-label">Panicked</div>
</div>
</div>
<div class="card full-width">
<h2>Transaction Statistics</h2>
<div id="instruction-stats"></div>
</div>
<div class="charts-grid">
<div class="card">
<h2>Success Rate Overview</h2>
<div class="chart-container">
<canvas id="successChart"></canvas>
</div>
</div>
<div class="card">
<h2>Error Breakdown</h2>
<div class="chart-container">
<canvas id="errorChart"></canvas>
</div>
</div>
</div>
<div class="card full-width">
<h2>Error Details</h2>
<div id="error-details"></div>
</div>
<div class="card full-width">
<h2>Panic Analysis</h2>
<div id="panic-analysis"></div>
</div>
<div class="card full-width">
<h2>Custom Metrics</h2>
<div id="custom-metrics"></div>
</div>
<div class="card full-width" id="regression-testing-section">
<h2>Regression Testing</h2>
<div class="metric" id="state-hash-metric" style="display: none;">
<span>Regression Data Hash</span>
<span class="metric-value" id="state-hash" style="font-family: 'Courier New', monospace; font-size: 0.9em; word-break: break-all;"></span>
</div>
<div id="no-state-info" style="text-align: center; padding: 20px; color: #64748b;">
<p>No regression data available for this fuzzing session.</p>
<p style="font-size: 0.9em; margin-top: 8px;">To enable regression testing:</p>
<p style="font-size: 0.9em; margin-top: 4px;">1. Add <code style="background: #334155; padding: 2px 6px; border-radius: 4px;">[fuzz.regression]<br/>enabled = true</code> in your Trident.toml</p>
<p style="font-size: 0.9em; margin-top: 4px;">2. Add <code style="background: #334155; padding: 2px 6px; border-radius: 4px;">self.trident.add_to_regression(...)</code> in your fuzzing tests</p>
</div>
<div id="state-info" style="display: none;">
<p style="color: #94a3b8; font-size: 0.9em; margin-top: 12px;">
The regression data hash represents the aggregated state of all account snapshots captured during fuzzing.
It can be used to verify consistency across different fuzzing runs <strong>when using the same master seed</strong> for regression testing purposes.
</p>
</div>
</div>
<div class="card full-width" style="margin-top: 20px;">
<h2>Raw Data</h2>
<details>
<summary style="cursor: pointer; color: #38bdf8; padding: 8px;">View raw JSON data</summary>
<pre id="raw-data" style="background: #0f172a; color: #cbd5e1; padding: 15px; border-radius: 6px; overflow: auto; max-height: 400px; border: 1px solid #475569; margin-top: 10px;"></pre>
</details>
</div>
<div class="footer">
<p>Generated by Trident Fuzzing Framework</p>
</div>
</div>
<script>
const data = {{JSON_DATA}};
document.getElementById('timestamp').textContent = new Date().toLocaleString();
function formatLargeNumber(num) {
if (num === undefined || num === null) return '0';
const absNum = Math.abs(num);
if (absNum >= 1e12) {
return (num / 1e12).toFixed(1) + 'T';
} else if (absNum >= 1e9) {
return (num / 1e9).toFixed(1) + 'B';
} else if (absNum >= 1e6) {
return (num / 1e6).toFixed(1) + 'M';
} else if (absNum >= 1e3) {
return (num / 1e3).toFixed(1) + 'K';
}
return num.toLocaleString();
}
function formatNumberWithSize(num, element) {
const formatted = formatLargeNumber(num);
if (element) {
if (formatted.length > 8) {
element.style.fontSize = '1.2rem';
} else if (formatted.length > 6) {
element.style.fontSize = '1.5rem';
} else {
element.style.fontSize = '1.8rem';
}
}
return formatted;
}
document.getElementById('raw-data').textContent = JSON.stringify(data, null, 2);
function calculateSummaryStats() {
let totalTransactions = 0;
let totalSuccessful = 0;
let totalFailed = 0;
let totalPanicked = 0;
for (const [instruction, stats] of Object.entries(data.instructions)) {
totalTransactions += stats.invoked || 0;
totalSuccessful += stats.transactions_successful || 0;
totalFailed += stats.transactions_failed || 0;
totalPanicked += stats.transactions_panicked || 0;
}
document.getElementById('total-transactions').textContent = totalTransactions.toLocaleString();
document.getElementById('total-successful').textContent = totalSuccessful.toLocaleString();
document.getElementById('total-failed').textContent = totalFailed.toLocaleString();
document.getElementById('total-panicked').textContent = totalPanicked.toLocaleString();
}
function generateInstructionStats() {
const container = document.getElementById('instruction-stats');
let html = '';
for (const [instruction, stats] of Object.entries(data.instructions)) {
const successRate = stats.invoked > 0 ? ((stats.transactions_successful / stats.invoked) * 100).toFixed(1) : 0;
html += `
<div style="background: #334155; border-radius: 8px; padding: 16px; margin-bottom: 12px; border: 1px solid #475569;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h3 style="margin: 0; color: #e2e8f0; font-size: 1.1rem;">${instruction}</h3>
<div style="background: ${successRate >= 90 ? '#22c55e' : successRate >= 70 ? '#f59e0b' : '#ef4444'}; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.9rem; font-weight: bold;">
${successRate}% Success
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px;">
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: bold; color: #38bdf8;">${stats.invoked.toLocaleString()}</div>
<div style="color: #94a3b8; font-size: 0.9rem;">Total Invoked</div>
</div>
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: bold; color: #22c55e;">${stats.transactions_successful.toLocaleString()}</div>
<div style="color: #94a3b8; font-size: 0.9rem;">Successful</div>
</div>
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: bold; color: #ef4444;">${stats.transactions_failed.toLocaleString()}</div>
<div style="color: #94a3b8; font-size: 0.9rem;">Failed</div>
</div>
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: bold; color: #a855f7;">${stats.transactions_panicked.toLocaleString()}</div>
<div style="color: #94a3b8; font-size: 0.9rem;">Panicked</div>
</div>
</div>
</div>
`;
}
container.innerHTML = html;
}
function generateSuccessChart() {
const ctx = document.getElementById('successChart').getContext('2d');
const labels = Object.keys(data.instructions);
const successData = labels.map(key => data.instructions[key].transactions_successful);
const failedData = labels.map(key => data.instructions[key].transactions_failed);
new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Successful',
data: successData,
backgroundColor: '#22c55e'
}, {
label: 'Failed',
data: failedData,
backgroundColor: '#ef4444'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: '#e2e8f0'
}
}
},
scales: {
x: {
stacked: true,
ticks: {
color: '#94a3b8'
},
grid: {
color: '#334155'
}
},
y: {
stacked: true,
ticks: {
color: '#94a3b8'
},
grid: {
color: '#334155'
}
}
}
}
});
}
function generateErrorChart() {
const errorCounts = {};
const customErrorCounts = {};
for (const [instruction, stats] of Object.entries(data.instructions)) {
for (const [error, errorData] of Object.entries(stats.transactions_errors || {})) {
if (!errorCounts[error] || errorData.occurrences > errorCounts[error]) {
errorCounts[error] = errorData.occurrences;
}
}
for (const [errorCode, errorData] of Object.entries(stats.custom_instruction_errors || {})) {
const errorLabel = `Custom Error ${errorCode}`;
if (!customErrorCounts[errorLabel] || errorData.occurrences > customErrorCounts[errorLabel]) {
customErrorCounts[errorLabel] = errorData.occurrences;
}
}
}
const allErrors = {...errorCounts, ...customErrorCounts};
const ctx = document.getElementById('errorChart').getContext('2d');
const labels = Object.keys(allErrors);
const counts = Object.values(allErrors);
if (labels.length > 0) {
new Chart(ctx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
data: counts,
backgroundColor: [
'#ef4444', '#f59e0b', '#8b5cf6', '#06b6d4',
'#10b981', '#ec4899', '#6366f1', '#84cc16',
'#f97316', '#14b8a6', '#a855f7', '#eab308'
],
borderColor: '#1e293b',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
color: '#e2e8f0',
padding: 15,
font: {
size: 12
}
}
},
tooltip: {
backgroundColor: '#1e293b',
titleColor: '#e2e8f0',
bodyColor: '#e2e8f0',
borderColor: '#334155',
borderWidth: 1
}
}
}
});
} else {
document.getElementById('errorChart').parentElement.innerHTML = '<h2>Error Breakdown</h2><p style="text-align: center; color: #22c55e; font-size: 1.2em;">No errors detected!</p>';
}
}
function formatSolanaLogs(logs) {
if (!logs || logs.length === 0) return 'No logs available';
return logs.map(log => {
let formattedLog = log.replace(/\\n/g, '\n');
if (formattedLog.includes('Program') && formattedLog.includes('invoke')) {
formattedLog = `<span class="log-invoke">${formattedLog}</span>`;
} else if (formattedLog.includes('Program') && formattedLog.includes('success')) {
formattedLog = `<span class="log-success">${formattedLog}</span>`;
} else if (formattedLog.includes('Program') && formattedLog.includes('failed')) {
formattedLog = `<span class="log-failed">${formattedLog}</span>`;
} else if (formattedLog.includes('Program log:')) {
formattedLog = `<span class="log-message">${formattedLog}</span>`;
} else if (formattedLog.includes('consumed') && formattedLog.includes('compute units')) {
formattedLog = `<span class="log-compute">${formattedLog}</span>`;
} else if (formattedLog.includes('Program') && formattedLog.includes('error')) {
formattedLog = `<span class="log-error">${formattedLog}</span>`;
} else if (formattedLog.startsWith('Program ')) {
formattedLog = `<span class="log-program">${formattedLog}</span>`;
}
return `<div class="log-line">${formattedLog}</div>`;
}).join('');
}
function formatTransactionInputs(inputData) {
if (!inputData) {
return 'No instruction data available';
}
try {
if (typeof inputData === 'object') {
return formatTransactionJsonStructure(inputData, 0);
}
if (typeof inputData === 'string') {
try {
const parsed = JSON.parse(inputData);
return formatTransactionJsonStructure(parsed, 0);
} catch (e) {
return formatDebugString(inputData);
}
}
return formatDebugString(String(inputData));
} catch (e) {
return `Error formatting inputs: ${e.message}`;
}
}
function formatTransactionJsonStructure(obj, indent = 0) {
const indentStyle = `margin-left: ${indent * 20}px;`;
const nextIndentStyle = `margin-left: ${(indent + 1) * 20}px;`;
if (obj === null) {
return 'null';
}
if (typeof obj === 'boolean') {
return String(obj);
}
if (typeof obj === 'number') {
return String(obj);
}
if (typeof obj === 'string') {
return `"${obj}"`;
}
if (Array.isArray(obj)) {
if (obj.length === 0) {
return '[]';
}
let result = '<div>[';
obj.forEach((item, index) => {
result += `<div style="${nextIndentStyle}">`;
result += formatTransactionJsonStructure(item, indent + 1);
if (index < obj.length - 1) {
result += ',';
}
result += '</div>';
});
result += `<div style="${indentStyle}">]</div></div>`;
return result;
}
if (typeof obj === 'object') {
const keys = Object.keys(obj);
if (keys.length === 0) {
return '{}';
}
let result = '<div>{';
keys.forEach((key, index) => {
const value = obj[key];
result += `<div style="${nextIndentStyle}">`;
result += `"${key}": `;
result += formatTransactionJsonStructure(value, indent + 1);
if (index < keys.length - 1) {
result += ',';
}
result += '</div>';
});
result += `<div style="${indentStyle}">}</div></div>`;
return result;
}
return String(obj);
}
function formatJsonStructure(obj, indent = 0) {
const indentStyle = `margin-left: ${indent * 20}px;`;
const nextIndentStyle = `margin-left: ${(indent + 1) * 20}px;`;
if (obj === null) {
return 'null';
}
if (typeof obj === 'boolean') {
return String(obj);
}
if (typeof obj === 'number') {
return String(obj);
}
if (typeof obj === 'string') {
return `"${obj}"`;
}
if (Array.isArray(obj)) {
if (obj.length === 0) {
return '[]';
}
let result = '<div>[';
obj.forEach((item, index) => {
result += `<div style="${nextIndentStyle}">`;
result += formatJsonStructure(item, indent + 1);
if (index < obj.length - 1) {
result += ',';
}
result += '</div>';
});
result += `<div style="${indentStyle}">]</div></div>`;
return result;
}
if (typeof obj === 'object') {
const keys = Object.keys(obj);
if (keys.length === 0) {
return '{}';
}
let result = '<div>{';
keys.forEach((key, index) => {
const value = obj[key];
result += `<div style="${nextIndentStyle}">`;
result += `"${key}": `;
result += formatJsonStructure(value, indent + 1);
if (index < keys.length - 1) {
result += ',';
}
result += '</div>';
});
result += `<div style="${indentStyle}">}</div></div>`;
return result;
}
return String(obj);
}
function formatDebugString(inputString) {
if (!inputString) return 'No instruction data available';
return String(inputString)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/\n/g, '<br>')
.replace(/\s/g, ' ');
}
function generatePanicAnalysis() {
const container = document.getElementById('panic-analysis');
let html = '';
let totalPanics = 0;
for (const [instruction, stats] of Object.entries(data.instructions)) {
const panics = stats.transactions_panics || {};
const panicKeys = Object.keys(panics);
if (panicKeys.length > 0) {
totalPanics += panicKeys.length;
html += `
<div style="background: #334155; border-radius: 8px; padding: 20px; margin-bottom: 16px; border-left: 4px solid #a855f7;">
<h3 style="margin: 0 0 16px 0; color: #e2e8f0; font-size: 1.1rem; display: flex; align-items: center;">
<span style="background: #a855f7; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.9rem; margin-right: 12px;">${instruction}</span>
${panicKeys.length} panic${panicKeys.length > 1 ? 's' : ''}
</h3>
`;
for (const [panic, panicData] of Object.entries(panics)) {
const panicId = `panic-${instruction.replace(/\W/g, '_')}-${totalPanics}`;
const inputsId = `inputs-${instruction.replace(/\W/g, '_')}-${totalPanics}`;
html += `
<div class="error-section" style="margin-left: 0; margin-bottom: 12px;">
<div class="error-title">${panic}</div>
<div class="error-count">Occurred ${panicData.occurrences} times | Seed: ${panicData.seed}</div>
<div class="collapsible" onclick="toggleCollapsible('${panicId}')">
View Panic Logs ▼
</div>
<div id="${panicId}" class="collapsible-content">
<div class="error-logs">${formatSolanaLogs(panicData.logs)}</div>
</div>
${panicData.instruction_inputs ? `
<div class="collapsible" onclick="toggleCollapsible('${inputsId}')">
View Instruction Data ▼
</div>
<div id="${inputsId}" class="collapsible-content">
<div class="error-logs" style="white-space: pre-wrap;">${formatDebugString(panicData.instruction_inputs)}</div>
</div>
` : ''}
</div>
`;
}
html += '</div>';
}
}
if (totalPanics === 0) {
html = '<p style="text-align: center; color: #22c55e; font-size: 1.2em;">No panics detected!</p>';
}
container.innerHTML = html;
}
function generateErrorDetails() {
const container = document.getElementById('error-details');
let html = '';
let globalErrorCount = 0;
for (const [instruction, stats] of Object.entries(data.instructions)) {
const transactionErrors = stats.transactions_errors || {};
const customErrors = stats.custom_instruction_errors || {};
const totalErrorsForTransaction = Object.keys(transactionErrors).length + Object.keys(customErrors).length;
if (totalErrorsForTransaction > 0) {
html += `
<div style="background: #334155; border-radius: 8px; padding: 20px; margin-bottom: 16px; border-left: 4px solid #ef4444;">
<h3 style="margin: 0 0 16px 0; color: #e2e8f0; font-size: 1.1rem; display: flex; align-items: center;">
<span style="background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px; font-size: 0.9rem; margin-right: 12px;">${instruction}</span>
${totalErrorsForTransaction} error${totalErrorsForTransaction > 1 ? 's' : ''}
</h3>
`;
for (const [error, errorData] of Object.entries(transactionErrors)) {
globalErrorCount++;
const errorId = `error-${instruction.replace(/\W/g, '_')}-${globalErrorCount}`;
html += `
<div class="error-section" style="margin-left: 0; margin-bottom: 12px;">
<div class="error-title">${error}</div>
<div class="error-count">Occurred ${errorData.occurrences} times</div>
<div class="collapsible" onclick="toggleCollapsible('${errorId}')">
View Error Logs ▼
</div>
<div id="${errorId}" class="collapsible-content">
<div class="error-logs">${formatSolanaLogs(errorData.logs)}</div>
</div>
</div>
`;
}
for (const [errorCode, errorData] of Object.entries(customErrors)) {
globalErrorCount++;
const errorId = `error-${instruction.replace(/\W/g, '_')}-${globalErrorCount}`;
html += `
<div class="error-section" style="margin-left: 0; margin-bottom: 12px;">
<div class="error-title">Custom Error Code ${errorCode}</div>
<div class="error-count">Occurred ${errorData.occurrences} times</div>
<div class="collapsible" onclick="toggleCollapsible('${errorId}')">
View Error Logs ▼
</div>
<div id="${errorId}" class="collapsible-content">
<div class="error-logs">${formatSolanaLogs(errorData.logs)}</div>
</div>
</div>
`;
}
html += '</div>';
}
}
if (globalErrorCount === 0) {
html = '<p style="text-align: center; color: #22c55e; font-size: 1.2em;">No detailed error information available!</p>';
}
container.innerHTML = html;
}
function toggleCollapsible(id) {
const element = document.getElementById(id);
const button = element.previousElementSibling;
let contentType = 'Logs';
if (button.innerHTML.includes('Panic Logs')) {
contentType = 'Panic Logs';
} else if (button.innerHTML.includes('Instruction Data')) {
contentType = 'Instruction Data';
} else if (button.innerHTML.includes('Error Logs')) {
contentType = 'Error Logs';
} else if (button.innerHTML.includes('Transaction Inputs')) {
contentType = 'Transaction Inputs';
}
if (element.style.display === 'block') {
element.style.display = 'none';
button.innerHTML = `View ${contentType} ▼`;
} else {
element.style.display = 'block';
button.innerHTML = `Hide ${contentType} ▲`;
}
}
function generateCustomMetrics() {
const container = document.getElementById('custom-metrics');
if (!data.custom_metrics || Object.keys(data.custom_metrics).length === 0) {
container.innerHTML = `
<div class="no-metrics-placeholder">
<div class="no-metrics-icon">📊</div>
<h3 style="margin: 0 0 8px 0; color: #64748b;">No Custom Metrics Available</h3>
<p style="margin: 0; font-size: 0.9rem;">Add custom metrics to your fuzzing tests to see detailed analytics here.</p>
</div>
`;
return;
}
const metricsHtml = Object.entries(data.custom_metrics)
.map(([metricName, metricData]) => generateMetricCard(metricName, metricData))
.join('');
container.innerHTML = `<div class="metrics-grid">${metricsHtml}</div>`;
}
function generateMetricCard(name, data) {
const displayName = name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
if (data.Accumulator !== undefined) {
return `
<div class="metric-card">
<div class="metric-header">
<h3 class="metric-title">${displayName}</h3>
<span class="metric-type-badge accumulator-badge">Accumulator</span>
</div>
<div class="metric-with-info">
<span>Total Value</span>
<span class="metric-value">${data.Accumulator.toLocaleString()}</span>
</div>
</div>
`;
} else if (data.Histogram !== undefined) {
const hist = data.Histogram;
const maxEntropy = Math.log2(hist.count);
const entropyRatio = hist.entropy / maxEntropy;
const entropyClass = entropyRatio < 0.3 ? 'entropy-low' :
entropyRatio < 0.6 ? 'entropy-medium' :
entropyRatio < 0.8 ? 'entropy-good' : 'entropy-excellent';
return `
<div class="metric-card">
<div class="metric-header">
<h3 class="metric-title">${displayName}</h3>
<span class="metric-type-badge histogram-badge">Distribution</span>
</div>
<div class="metric-with-info">
<div class="metric-label-container">
<span>Count</span>
<span class="info-icon">i
<div class="tooltip">
<strong>Sample Count</strong><br>
The total number of data points collected for this metric during fuzzing.
</div>
</span>
</div>
<span class="metric-value">${hist.count.toLocaleString()}</span>
</div>
<div class="metric-with-info">
<div class="metric-label-container">
<span>Range</span>
<span class="info-icon">i
<div class="tooltip">
<strong>Min - Max Range</strong><br>
The smallest and largest values observed. Shows the spread of your data.
</div>
</span>
</div>
<span class="metric-value">${hist.min.toFixed(2)} - ${hist.max.toFixed(2)}</span>
</div>
<div class="metric-with-info">
<div class="metric-label-container">
<span>Average</span>
<span class="info-icon">i
<div class="tooltip">
<strong>Arithmetic Mean</strong><br>
The sum of all values divided by the count. Represents the typical/expected value but can be skewed by outliers.
</div>
</span>
</div>
<span class="metric-value">${hist.avg.toFixed(2)}</span>
</div>
<div class="metric-with-info">
<div class="metric-label-container">
<span>Median</span>
<span class="info-icon">i
<div class="tooltip">
<strong>Middle Value</strong><br>
The value that separates the higher half from the lower half. Less affected by outliers than the average.
</div>
</span>
</div>
<span class="metric-value">${hist.median.toFixed(2)}</span>
</div>
<div class="metric-with-info">
<div class="metric-label-container">
<span>Shannon Entropy</span>
<span class="entropy-indicator ${entropyClass}"></span>
<span class="info-icon">i
<div class="tooltip">
<strong>Shannon Entropy</strong><br>
Measures data diversity (0 to log₂(count)). The color indicator shows entropy relative to the theoretical maximum for your sample count:<br>
• <span style="color: #ef4444;">Red</span>: <30% of max (low diversity)<br>
• <span style="color: #f59e0b;">Orange</span>: 30-60% of max (medium)<br>
• <span style="color: #22c55e;">Green</span>: 60-80% of max (good)<br>
• <span style="color: #06b6d4;">Cyan</span>: >80% of max (excellent)
</div>
</span>
</div>
<span class="metric-value">${hist.entropy.toFixed(4)}</span>
</div>
</div>
`;
}
return '';
}
function displayRegressionTesting() {
const masterSeedValue = document.getElementById('master-seed-value');
const stateHashMetric = document.getElementById('state-hash-metric');
const stateHashElement = document.getElementById('state-hash');
const noStateInfo = document.getElementById('no-state-info');
const stateInfo = document.getElementById('state-info');
let hasRegressionData = false;
if (data.master_seed && data.master_seed !== null) {
masterSeedValue.textContent = data.master_seed;
masterSeedValue.style.color = '#e2e8f0';
} else {
masterSeedValue.textContent = 'Not available';
masterSeedValue.style.color = '#64748b';
}
if (data.state_snapshots_hash) {
stateHashMetric.style.display = 'flex';
stateHashElement.textContent = data.state_snapshots_hash;
stateInfo.style.display = 'block';
hasRegressionData = true;
}
if (hasRegressionData) {
noStateInfo.style.display = 'none';
} else {
noStateInfo.style.display = 'block';
}
}
calculateSummaryStats();
generateInstructionStats();
generateSuccessChart();
generateErrorChart();
generatePanicAnalysis();
generateErrorDetails();
generateCustomMetrics();
displayRegressionTesting();
</script>
</body>
</html>