Skip to content

Commit 7fdd7e6

Browse files
committed
JSON file detection with SOROBAN and Impl all done
1 parent 207f912 commit 7fdd7e6

10 files changed

Lines changed: 2387 additions & 0 deletions
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import {
2+
analyzeFootprintAccess,
3+
FootprintAccessReport,
4+
} from "../footprint-access-analyzer";
5+
6+
describe("SorobanFootprintAccessAnalyzer", () => {
7+
describe("analyzeFootprintAccess", () => {
8+
it("should return empty report for source with no storage accesses", () => {
9+
const source = `
10+
pub fn noop() {}
11+
`;
12+
const report = analyzeFootprintAccess(source);
13+
expect(report.findings).toHaveLength(0);
14+
expect(report.metrics.totalKeys).toBe(0);
15+
expect(report.summary).toContain("No footprint access patterns detected");
16+
});
17+
18+
it("should detect hot keys with high access frequency", () => {
19+
const source = `
20+
const BALANCE: Symbol = Symbol::new(&env, "balance");
21+
22+
pub fn process_transfers() {
23+
let a: i128 = env.storage().persistent().get(&BALANCE);
24+
let b: i128 = env.storage().persistent().get(&BALANCE);
25+
let c: i128 = env.storage().persistent().get(&BALANCE);
26+
let d: i128 = env.storage().persistent().get(&BALANCE);
27+
let e: i128 = env.storage().persistent().get(&BALANCE);
28+
let f: i128 = env.storage().persistent().get(&BALANCE);
29+
}
30+
`;
31+
const report = analyzeFootprintAccess(source);
32+
33+
const hotKey = report.findings.find((f) => f.pattern === "hot_key");
34+
expect(hotKey).toBeDefined();
35+
expect(hotKey!.ruleId).toBe("SOROBAN-FPA-01");
36+
expect(hotKey!.affectedKeys).toContain("balance");
37+
});
38+
39+
it("should detect cold keys with minimal access", () => {
40+
const source = `
41+
const RARE_KEY: Symbol = Symbol::new(&env, "rare_key");
42+
43+
pub fn rarely_used() -> i128 {
44+
env.storage().persistent().get(&RARE_KEY)
45+
}
46+
`;
47+
const report = analyzeFootprintAccess(source);
48+
49+
const coldKey = report.findings.find((f) => f.pattern === "cold_key");
50+
expect(coldKey).toBeDefined();
51+
expect(coldKey!.ruleId).toBe("SOROBAN-FPA-02");
52+
expect(coldKey!.affectedKeys).toContain("rare_key");
53+
});
54+
55+
it("should detect loop access patterns", () => {
56+
const source = `
57+
const ITEM: Symbol = Symbol::new(&env, "item");
58+
59+
pub fn process_loop(items: Vec<i128>) {
60+
for i in 0..items.len() {
61+
let val: i128 = env.storage().persistent().get(&ITEM);
62+
env.storage().persistent().set(&ITEM, &val);
63+
}
64+
}
65+
`;
66+
const report = analyzeFootprintAccess(source);
67+
68+
const loopAccess = report.findings.find((f) => f.pattern === "loop_access");
69+
expect(loopAccess).toBeDefined();
70+
expect(loopAccess!.ruleId).toBe("SOROBAN-FPA-03");
71+
expect(loopAccess!.severity).toBe("high");
72+
});
73+
74+
it("should detect imbalanced access patterns", () => {
75+
const source = `
76+
const CONFIG: Symbol = Symbol::new(&env, "config");
77+
78+
pub fn read_heavy() -> i128 {
79+
let a: i128 = env.storage().persistent().get(&CONFIG);
80+
let b: i128 = env.storage().persistent().get(&CONFIG);
81+
let c: i128 = env.storage().persistent().get(&CONFIG);
82+
let d: i128 = env.storage().persistent().get(&CONFIG);
83+
let e: i128 = env.storage().persistent().get(&CONFIG);
84+
env.storage().persistent().set(&CONFIG, &0);
85+
}
86+
`;
87+
const report = analyzeFootprintAccess(source);
88+
89+
const imbalanced = report.findings.find((f) => f.pattern === "imbalanced_access");
90+
expect(imbalanced).toBeDefined();
91+
expect(imbalanced!.ruleId).toBe("SOROBAN-FPA-04");
92+
});
93+
94+
it("should detect read-heavy functions", () => {
95+
const source = `
96+
const A: Symbol = Symbol::new(&env, "a");
97+
const B: Symbol = Symbol::new(&env, "b");
98+
const C: Symbol = Symbol::new(&env, "c");
99+
100+
pub fn read_heavy_fn() -> i128 {
101+
let a: i128 = env.storage().persistent().get(&A);
102+
let b: i128 = env.storage().persistent().get(&B);
103+
let c: i128 = env.storage().persistent().get(&C);
104+
let d: i128 = env.storage().persistent().get(&A);
105+
let e: i128 = env.storage().persistent().get(&B);
106+
env.storage().persistent().set(&C, &0);
107+
}
108+
`;
109+
const report = analyzeFootprintAccess(source);
110+
111+
const readHeavy = report.findings.find((f) => f.pattern === "read_heavy");
112+
expect(readHeavy).toBeDefined();
113+
expect(readHeavy!.ruleId).toBe("SOROBAN-FPA-05");
114+
});
115+
116+
it("should detect write-heavy functions", () => {
117+
const source = `
118+
const X: Symbol = Symbol::new(&env, "x");
119+
const Y: Symbol = Symbol::new(&env, "y");
120+
const Z: Symbol = Symbol::new(&env, "z");
121+
122+
pub fn write_heavy_fn() {
123+
env.storage().persistent().set(&X, &1);
124+
env.storage().persistent().set(&Y, &2);
125+
env.storage().persistent().set(&Z, &3);
126+
env.storage().persistent().set(&X, &4);
127+
env.storage().persistent().set(&Y, &5);
128+
let _: i128 = env.storage().persistent().get(&Z);
129+
}
130+
`;
131+
const report = analyzeFootprintAccess(source);
132+
133+
const writeHeavy = report.findings.find((f) => f.pattern === "write_heavy");
134+
expect(writeHeavy).toBeDefined();
135+
expect(writeHeavy!.ruleId).toBe("SOROBAN-FPA-06");
136+
});
137+
138+
it("should detect high footprint overlap between functions", () => {
139+
const source = `
140+
const A: Symbol = Symbol::new(&env, "a");
141+
const B: Symbol = Symbol::new(&env, "b");
142+
const C: Symbol = Symbol::new(&env, "c");
143+
const D: Symbol = Symbol::new(&env, "d");
144+
145+
pub fn fn_one() -> i128 {
146+
let a: i128 = env.storage().persistent().get(&A);
147+
let b: i128 = env.storage().persistent().get(&B);
148+
let c: i128 = env.storage().persistent().get(&C);
149+
let d: i128 = env.storage().persistent().get(&D);
150+
a + b + c + d
151+
}
152+
153+
pub fn fn_two() -> i128 {
154+
let a: i128 = env.storage().persistent().get(&A);
155+
let b: i128 = env.storage().persistent().get(&B);
156+
let c: i128 = env.storage().persistent().get(&C);
157+
let d: i128 = env.storage().persistent().get(&D);
158+
a * b * c * d
159+
}
160+
`;
161+
const report = analyzeFootprintAccess(source);
162+
163+
const highOverlap = report.findings.find((f) => f.pattern === "high_overlap");
164+
expect(highOverlap).toBeDefined();
165+
expect(highOverlap!.ruleId).toBe("SOROBAN-FPA-07");
166+
});
167+
168+
it("should build function profiles correctly", () => {
169+
const source = `
170+
const A: Symbol = Symbol::new(&env, "a");
171+
const B: Symbol = Symbol::new(&env, "b");
172+
173+
pub fn profile_test() -> i128 {
174+
let a: i128 = env.storage().persistent().get(&A);
175+
let b: i128 = env.storage().persistent().get(&B);
176+
env.storage().persistent().set(&A, &0);
177+
a + b
178+
}
179+
`;
180+
const report = analyzeFootprintAccess(source);
181+
182+
expect(report.functionProfiles.length).toBeGreaterThan(0);
183+
const profile = report.functionProfiles.find((p) => p.functionName === "profile_test");
184+
expect(profile).toBeDefined();
185+
expect(profile!.keysAccessed).toContain("a");
186+
expect(profile!.keysAccessed).toContain("b");
187+
expect(profile!.readCount).toBe(2);
188+
expect(profile!.writeCount).toBe(1);
189+
});
190+
191+
it("should report accurate metrics", () => {
192+
const source = `
193+
const A: Symbol = Symbol::new(&env, "a");
194+
const B: Symbol = Symbol::new(&env, "b");
195+
196+
pub fn metrics_test() -> i128 {
197+
let a: i128 = env.storage().persistent().get(&A);
198+
let b: i128 = env.storage().persistent().get(&B);
199+
env.storage().persistent().set(&A, &42);
200+
a + b
201+
}
202+
`;
203+
const report = analyzeFootprintAccess(source);
204+
205+
expect(report.metrics.totalKeys).toBe(2);
206+
expect(report.metrics.totalReads).toBe(2);
207+
expect(report.metrics.totalWrites).toBe(1);
208+
expect(report.metrics.averageAccessDensity).toBeGreaterThan(0);
209+
});
210+
211+
it("should track access across multiple storage tiers", () => {
212+
const source = `
213+
const INSTANCE_KEY: Symbol = Symbol::new(&env, "instance_key");
214+
const PERSISTENT_KEY: Symbol = Symbol::new(&env, "persistent_key");
215+
const TEMP_KEY: Symbol = Symbol::new(&env, "temp_key");
216+
217+
pub fn multi_tier() {
218+
env.storage().instance().get(&INSTANCE_KEY);
219+
env.storage().persistent().get(&PERSISTENT_KEY);
220+
env.storage().temporary().get(&TEMP_KEY);
221+
}
222+
`;
223+
const report = analyzeFootprintAccess(source);
224+
225+
expect(report.metrics.totalKeys).toBe(3);
226+
227+
const keys = Object.values(report.keyAccessMap);
228+
const tiers = keys.map((k) => k.storageTier);
229+
expect(tiers).toContain("instance");
230+
expect(tiers).toContain("persistent");
231+
expect(tiers).toContain("temporary");
232+
});
233+
});
234+
});

0 commit comments

Comments
 (0)