Skip to content

Commit 56fda9c

Browse files
authored
Merge pull request #42 from Divineifed1/main
IMPLEMENTATION OF END TO END SCAN TESTING FEATURE
2 parents 1978426 + 8e434fb commit 56fda9c

6 files changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { SorobanAnalyzer } from "./src/languages/soroban.analyzer";
2+
export type ScanInput = {
3+
language: 'soroban' | 'solidity' | 'vyper';
4+
source: string;
5+
};
6+
7+
export type ScanResult = {
8+
issues: any[];
9+
};
10+
11+
export class GasGuardEngine {
12+
async scan(input: ScanInput): Promise<ScanResult> {
13+
switch (input.language) {
14+
case 'soroban':
15+
return new SorobanAnalyzer().analyze(input.source);
16+
17+
default:
18+
throw new Error(`Unsupported language: ${input.language}`);
19+
}
20+
}
21+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export class SorobanAnalyzer {
2+
analyze(source: string) {
3+
const issues = [];
4+
5+
if (source.includes('storage().instance().get')) {
6+
issues.push({
7+
ruleId: 'SOROBAN_STORAGE_REDUNDANT_READ',
8+
severity: 'medium',
9+
message: 'Repeated storage reads detected',
10+
suggestion: 'Cache storage value in a local variable',
11+
});
12+
}
13+
14+
if (source.includes('for') && source.includes('storage().instance().get')) {
15+
issues.push({
16+
ruleId: 'SOROBAN_LOOP_STORAGE_ACCESS',
17+
severity: 'high',
18+
message: 'Storage access inside loop detected',
19+
suggestion: 'Batch reads or redesign storage layout',
20+
});
21+
}
22+
23+
if (source.includes('.clone()')) {
24+
issues.push({
25+
ruleId: 'SOROBAN_REDUNDANT_CLONE',
26+
severity: 'low',
27+
message: 'Unnecessary clone detected',
28+
suggestion: 'Avoid cloning; return original or borrow',
29+
});
30+
}
31+
32+
return { issues };
33+
}
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![no_std]
2+
use soroban_sdk::{contract, contractimpl, Env};
3+
4+
#[contract]
5+
pub struct InefficientLoop;
6+
7+
#[contractimpl]
8+
impl InefficientLoop {
9+
pub fn sum(env: Env, n: u32) -> u32 {
10+
let mut total = 0;
11+
12+
// ❌ Inefficient: loop + storage access
13+
for i in 0..n {
14+
let val: u32 = env.storage().instance().get(&i).unwrap_or(0);
15+
total += val;
16+
}
17+
18+
total
19+
}
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![no_std]
2+
use soroban_sdk::{contract, contractimpl, Env, Symbol};
3+
4+
#[contract]
5+
pub struct InefficientStorage;
6+
7+
#[contractimpl]
8+
impl InefficientStorage {
9+
pub fn get_sum(env: Env) -> i32 {
10+
let key = Symbol::short("count");
11+
12+
// ❌ Inefficient: multiple storage reads
13+
let a: i32 = env.storage().instance().get(&key).unwrap_or(0);
14+
let b: i32 = env.storage().instance().get(&key).unwrap_or(0);
15+
16+
a + b
17+
}
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#![no_std]
2+
use soroban_sdk::{contract, contractimpl, Env, Vec};
3+
4+
#[contract]
5+
pub struct RedundantClone;
6+
7+
#[contractimpl]
8+
impl RedundantClone {
9+
pub fn duplicate(env: Env, data: Vec<i32>) -> Vec<i32> {
10+
11+
let copy = data.clone();
12+
copy
13+
}
14+
}
15+

tests/soroban_scan.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { GasGuardEngine } from '../packages/rules/gasGuard/gasguard.engine'
2+
import * as path from 'path';
3+
4+
describe('Soroban Full Scan Lifecycle', () => {
5+
const fixturesDir = path.join(
6+
__dirname,
7+
'../../../fixtures/soroban',
8+
);
9+
10+
const expectedDir = path.join(fixturesDir, 'expected');
11+
12+
let engine: GasGuardEngine;
13+
14+
beforeAll(() => {
15+
engine = new GasGuardEngine();
16+
});
17+
18+
it.each([
19+
'inefficient_storage.rs',
20+
'inefficient_loop.rs',
21+
'redundant_clone.rs',
22+
])('scans %s and returns expected findings', async (file) => {
23+
const source = fs.readFileSync(
24+
path.join(fixturesDir, file),
25+
'utf8',
26+
);
27+
28+
const expected = JSON.parse(
29+
fs.readFileSync(
30+
path.join(expectedDir, file.replace('.rs', '.json')),
31+
'utf8',
32+
),
33+
);
34+
35+
const result = await engine.scan({
36+
language: 'soroban',
37+
source,
38+
});
39+
40+
expect(result.issues).toEqual(expected.issues);
41+
});
42+
});

0 commit comments

Comments
 (0)