Skip to content

Commit dc358cd

Browse files
authored
Merge pull request #5 from saaj376/saajan
feat:semantic analysis and casual tracing done
2 parents 8437492 + 14b1616 commit dc358cd

9 files changed

Lines changed: 333 additions & 3 deletions

File tree

backend/contracts/Phase2TestContracts.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ contract TimeLockHoneypot {
3434
balanceOf[msg.sender] = totalSupply;
3535
}
3636

37+
// Allow easy testing via "Send ETH"
38+
receive() external payable {
39+
_checkTradingAllowed(msg.sender);
40+
}
41+
3742
modifier onlyOwner() {
3843
require(msg.sender == owner, "Not owner");
3944
_;
@@ -123,6 +128,11 @@ contract WhitelistHoneypot {
123128
isWhitelisted[msg.sender] = true;
124129
}
125130

131+
// Allow easy testing via "Send ETH"
132+
receive() external payable {
133+
_checkTransferAllowed(msg.sender);
134+
}
135+
126136
modifier onlyOwner() {
127137
require(msg.sender == owner, "Not owner");
128138
_;
@@ -219,6 +229,11 @@ contract DelayedTradingToken {
219229
balanceOf[msg.sender] = totalSupply;
220230
}
221231

232+
// Allow easy testing via "Send ETH"
233+
receive() external payable {
234+
_checkTradingOpen(msg.sender);
235+
}
236+
222237
modifier onlyOwner() {
223238
require(msg.sender == owner, "Not owner");
224239
_;

backend/scripts/verify_phase3.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
2+
// Native fetch is available in Node > 18
3+
4+
async function verifyPhase3() {
5+
console.log("Verifying Phase 3: Dynamic Causal Analysis...");
6+
7+
// 1. Test Whitelist Honeypot (expecting High Severity + specific story)
8+
const whitelistHoneypot = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512";
9+
console.log(`\n[1] Testing Whitelist Honeypot: ${whitelistHoneypot}`);
10+
11+
// Minimal transaction object
12+
const tx = {
13+
to: whitelistHoneypot,
14+
from: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", // Random user
15+
data: "0x",
16+
value: "0x0"
17+
};
18+
19+
try {
20+
const response = await fetch('http://127.0.0.1:3000/rpc', {
21+
method: 'POST',
22+
headers: { 'Content-Type': 'application/json' },
23+
body: JSON.stringify({
24+
jsonrpc: "2.0",
25+
id: 1,
26+
method: "sentinel_analyze",
27+
params: [tx, 31337]
28+
}),
29+
});
30+
31+
const data = await response.json();
32+
33+
if (data.error) {
34+
console.error("RPC Error:", data.error);
35+
return;
36+
}
37+
38+
const report = data.result.securityReport;
39+
40+
if (report.mechanismStory) {
41+
console.log("✅ Mechanism Story Found!");
42+
console.log("---------------------------------------------------");
43+
console.log(`Title: ${report.mechanismStory.title}`);
44+
console.log(`Severity: ${report.mechanismStory.severity}`);
45+
console.log(`Story: ${report.mechanismStory.story}`);
46+
console.log("---------------------------------------------------");
47+
48+
// Validation
49+
if (report.mechanismStory.severity === 'High') console.log("✅ Severity Check Passed");
50+
else console.log("❌ Severity Check Failed");
51+
52+
if (report.mechanismStory.title.includes("Owner Privileges") || report.mechanismStory.story.includes("Owner"))
53+
console.log("✅ Content Check Passed (Mentions Owner)");
54+
else console.log("❌ Content Check Failed (Missing Owner context)");
55+
56+
} else {
57+
console.log("❌ No Mechanism Story returned.");
58+
}
59+
60+
} catch (e) {
61+
console.error("Verification failed:", e);
62+
}
63+
}
64+
65+
verifyPhase3();
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
2+
import { TraceResult } from './OpcodeTracer.js';
3+
4+
export interface Explanation {
5+
title: string;
6+
story: string; // The "No-Jargon" story
7+
severity: 'High' | 'Medium' | 'Low' | 'Safe';
8+
}
9+
10+
export class ExplanationEngine {
11+
12+
static generateExplanation(trace: TraceResult, simulationStatus: string): Explanation {
13+
14+
// Default safe state
15+
let explanation: Explanation = {
16+
title: "Security Check Passed",
17+
story: "No suspicious mechanisms detected during execution.",
18+
severity: "Safe"
19+
};
20+
21+
if (simulationStatus.startsWith("Reverted")) {
22+
return this.explainRevert(trace);
23+
}
24+
25+
// Detect Time Logic (even if success)
26+
if (trace.usesTimestamp) {
27+
explanation = {
28+
title: "Time-Sensitive Logic Detected",
29+
story: "The contract checks the current time. It might be locked until a specific date.",
30+
severity: "Medium"
31+
};
32+
}
33+
34+
return explanation;
35+
}
36+
37+
private static explainRevert(trace: TraceResult): Explanation {
38+
// Did it revert after checking WHO sent it?
39+
if (trace.usesMsgSender || trace.usesTxOrigin) {
40+
// Heuristic: If we saw Sender -> Storage Read -> Revert, it's likely a Whitelist or Owner check
41+
const lastEvents = trace.events.slice(-5);
42+
const hasStorageCheck = lastEvents.some(e => e.includes("CHECK: Storage read"));
43+
44+
if (hasStorageCheck) {
45+
return {
46+
title: "Access Denied (Whitelist/Owner)",
47+
story: "❌ The contract checked who you are and blocked the transaction. It likely requires you to be on a private 'Whitelist' or be the Owner.",
48+
severity: "High"
49+
};
50+
}
51+
52+
return {
53+
title: "Sender Restriction",
54+
story: "❌ The contract blocked your address. Specific reason unclear, but it discriminates based on who sends the transaction.",
55+
severity: "Medium"
56+
};
57+
}
58+
59+
// Did it revert after checking TIME?
60+
if (trace.usesTimestamp) {
61+
return {
62+
title: "Time-Lock Active",
63+
story: "❌ The contract checked the time and decided it's too early (or too late) to trade.",
64+
severity: "High"
65+
};
66+
}
67+
68+
return {
69+
title: "Transaction Failed",
70+
story: "❌ The transaction reverted. This might be a standard error, or a hidden blocking mechanism.",
71+
severity: "Low"
72+
};
73+
}
74+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
2+
export interface ExecutionStep {
3+
opcode: string;
4+
pc: number;
5+
stack: string[];
6+
depth: number;
7+
memory?: string;
8+
}
9+
10+
export interface TraceResult {
11+
steps: ExecutionStep[];
12+
events: string[];
13+
revertReason?: string;
14+
touchedStorage: Set<string>;
15+
usesMsgSender: boolean;
16+
usesTxOrigin: boolean;
17+
usesTimestamp: boolean;
18+
suspiciousJumps: number;
19+
}
20+
21+
export class OpcodeTracer {
22+
private steps: ExecutionStep[] = [];
23+
private events: string[] = [];
24+
private touchedStorage = new Set<string>();
25+
private usesMsgSender = false;
26+
private usesTxOrigin = false;
27+
private usesTimestamp = false;
28+
private suspiciousJumps = 0;
29+
30+
// Track state for pattern matching (simple state machine)
31+
private lastOpcode: string | null = null;
32+
private pushedSender = false;
33+
34+
constructor() { }
35+
36+
handleStep(data: any) {
37+
const opcode = data.opcode.name;
38+
const pc = data.pc;
39+
const depth = data.depth;
40+
41+
// Capture stack (top 5 items for efficiency)
42+
const stack = data.stack ? data.stack.slice(-5).map((x: any) => x.toString(16)) : [];
43+
44+
this.steps.push({
45+
opcode,
46+
pc,
47+
stack,
48+
depth
49+
});
50+
51+
// Taint Analysis / Pattern Matching
52+
53+
// 1. Sender Tracking
54+
if (opcode === 'CALLER') { // msg.sender
55+
this.usesMsgSender = true;
56+
this.pushedSender = true;
57+
this.events.push(`TAINT: msg.sender loaded at PC ${pc}`);
58+
} else if (opcode === 'ORIGIN') { // tx.origin
59+
this.usesTxOrigin = true;
60+
this.events.push(`TAINT: tx.origin loaded at PC ${pc}`);
61+
}
62+
63+
// 2. Storage usage logic
64+
if (opcode === 'SLOAD') {
65+
const slot = stack[stack.length - 1]; // Top of stack is slot key
66+
this.touchedStorage.add(slot);
67+
if (this.pushedSender) {
68+
this.events.push(`CHECK: Storage read after Sender load - Potential Whitelist/Balance check`);
69+
}
70+
}
71+
72+
// 3. Time usage logic
73+
if (opcode === 'TIMESTAMP') {
74+
this.usesTimestamp = true;
75+
this.events.push(`TAINT: block.timestamp loaded at PC ${pc}`);
76+
}
77+
78+
// 4. Comparison Logic (EQ/LT/GT)
79+
if (['EQ', 'LT', 'GT', 'SGT', 'SLT'].includes(opcode)) {
80+
// If we just loaded sender or timestamp, this is a distinct check
81+
if (this.lastOpcode === 'CALLER' || this.lastOpcode === 'ORIGIN') {
82+
this.events.push(`CHECK: Comparing Sender address`);
83+
}
84+
if (this.lastOpcode === 'TIMESTAMP') {
85+
this.events.push(`CHECK: Comparing Timestamp`);
86+
}
87+
}
88+
89+
// 5. Control Flow (JUMPI)
90+
if (opcode === 'JUMPI') {
91+
// If JUMPI happens after a critical check, it's a decision point
92+
// This is minimal; real taint analysis requires stack tracing
93+
}
94+
95+
// Reset transient flags
96+
if (!['PUSH1', 'PUSH2', 'PUSH20', 'PUSH32', 'DUP1', 'DUP2'].includes(opcode)) {
97+
// Keep pushedSender tag alive only through direct data manipulation
98+
this.pushedSender = false;
99+
}
100+
101+
this.lastOpcode = opcode;
102+
}
103+
104+
getTrace(): TraceResult {
105+
return {
106+
steps: this.steps,
107+
events: this.events,
108+
touchedStorage: this.touchedStorage,
109+
usesMsgSender: this.usesMsgSender,
110+
usesTxOrigin: this.usesTxOrigin,
111+
usesTimestamp: this.usesTimestamp,
112+
suspiciousJumps: this.suspiciousJumps
113+
};
114+
}
115+
}

backend/src/analyzers/SecurityAnalyzer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export interface SecurityReport {
1818
analyzedAddress?: string; // The actual address analyzed (implementation if proxy)
1919
ownerAddress?: string; // [NEW] Needed for Counterfactual Simulation
2020
friendlyExplanation?: string;
21+
mechanismStory?: {
22+
title: string;
23+
story: string;
24+
severity: string;
25+
};
26+
tracingEvents?: string[];
2127
}
2228

2329
export class SecurityAnalyzer {

backend/src/evm/EvmExecutor.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { SecurityAnalyzer } from "../analyzers/SecurityAnalyzer.js";
66
import { ProxyDetector } from "../analyzers/ProxyDetector.js";
77
import { ScanHistory } from "../services/ScanHistory.js";
88
import { AdvancedSimulator } from "../analyzers/AdvancedSimulator.js";
9+
import { OpcodeTracer } from "../analyzers/OpcodeTracer.js";
10+
import { ExplanationEngine } from "../analyzers/ExplanationEngine.js";
911

1012
export class EvmExecutor {
1113
constructor() { }
@@ -114,16 +116,26 @@ export class EvmExecutor {
114116
let sstoreCount = 0;
115117
let callCount = 0;
116118

119+
const tracer = new OpcodeTracer();
120+
117121
evm.events.on('step', (data: any) => {
118122
if (instructionCount === 0) console.log("[Fork] First opcode executed:", data.opcode.name);
119123
instructionCount++;
120124
if (data.opcode.name === 'SSTORE') sstoreCount++;
121125
if (['CALL', 'DELEGATECALL', 'STATICCALL', 'CALLCODE'].includes(data.opcode.name)) callCount++;
126+
127+
// [PHASE 3] Dynamic Capability Tracing
128+
tracer.handleStep(data);
122129
});
123130

124131
console.log(`Executing Call: from=${sender.toString()} value=${txParams.value}`);
125132
const { status, result } = await this.executeCall(evm, txParams, sender);
126133

134+
// [PHASE 3] Generate Mechanism Story
135+
const traceResult = tracer.getTrace();
136+
const mechanismStory = ExplanationEngine.generateExplanation(traceResult, status);
137+
console.log("[Phase3] Mechanism Story:", mechanismStory.story);
138+
127139
console.log("EVM Execution Complete.");
128140
console.log(`Analysis: ${instructionCount} steps, ${sstoreCount} sstores`);
129141

@@ -176,6 +188,10 @@ export class EvmExecutor {
176188
console.log("Running Security Checks on:", addressToAnalyze.toString());
177189
securityReport = await SecurityAnalyzer.analyze(evm, addressToAnalyze, { status }, activeProvider);
178190

191+
// [PHASE 3] Attach Detective Insights
192+
securityReport.mechanismStory = mechanismStory;
193+
securityReport.tracingEvents = traceResult.events;
194+
179195
if (proxyInfo) {
180196
securityReport.proxyInfo = proxyInfo;
181197
if (proxyInfo.isProxy) {
@@ -237,6 +253,33 @@ export class EvmExecutor {
237253
console.warn("[Phase2] Advanced simulation failed:", advErr.message);
238254
}
239255

256+
// [PHASE 3 Refinement] Reconcile Phase 2 and Phase 3
257+
// If Phase 2 detected a scam (Revert/Honeypot) but Phase 3 trace (local) was Safe, it's likely due to missing storage.
258+
// We trust Phase 2 (RPC-based) more for outcomes.
259+
if (advancedAnalysis && advancedAnalysis.isScam && securityReport.mechanismStory.severity === 'Safe') {
260+
console.log("[Phase3] Reconciling: Overwriting Safe story with Phase 2 detection.");
261+
262+
if (advancedAnalysis.counterfactual.hasOwnerPrivileges) {
263+
securityReport.mechanismStory = {
264+
title: "Privilege Abuse Detected",
265+
story: "🕵️ The Detective noticed a discrepancy: Expected safe execution, but real-world simulation confirms only the OWNER can trade. This is a clear Honeypot.",
266+
severity: "High"
267+
};
268+
} else if (advancedAnalysis.timeTravel.isTimeSensitive) {
269+
securityReport.mechanismStory = {
270+
title: "Hidden Time-Lock",
271+
story: "🕵️ The Detective found that while code looks clean, it relies on time checks (likely uninitialized in scan) that strictly block trading.",
272+
severity: "High"
273+
};
274+
} else {
275+
securityReport.mechanismStory = {
276+
title: "Hidden Revert Mechanism",
277+
story: "🕵️ The execution path is misleading. Deep simulation confirms this transaction WILL fail for you, likely due to hidden storage dependencies.",
278+
severity: "High"
279+
};
280+
}
281+
}
282+
240283
console.log("Security Report:", securityReport);
241284

242285
const chainIdNum = typeof chainId === 'string' && chainId.includes(':') ? parseInt(chainId.split(':')[1]) : Number(chainId);

0 commit comments

Comments
 (0)