diff --git a/libs/engine/analyzers/solidity-analyzer.ts b/libs/engine/analyzers/solidity-analyzer.ts index adda7b8..7d49258 100644 --- a/libs/engine/analyzers/solidity-analyzer.ts +++ b/libs/engine/analyzers/solidity-analyzer.ts @@ -600,53 +600,53 @@ export class SolidityAnalyzer extends BaseAnalyzer implements Analyzer { ); } -// Rule: sol-016 - Storage Slot Collision - if (this.isRuleEnabled("sol-016", config)) { - const collisions = detectStorageSlotCollisions(code); - if (collisions.detected) { - findings.push( - ...collisions.collisions.map((collision) => ({ - ruleId: "sol-016", - message: collision.reason, - severity: this.getRuleSeverity("sol-016", config), - location: { - file: filePath, - startLine: collision.line1, - endLine: collision.line2, - }, - suggestedFix: { - description: collisions.suggestion, - documentationUrl: "https://docs.gasguard.dev/rules/sol-016", - }, - })), - ); - } - } - - // Rule: sol-017 - Missing Immutable Variables - if (this.isRuleEnabled("sol-017", config)) { - const missingImmutable = detectMissingImmutable(code); - if (missingImmutable.detected) { - findings.push( - ...missingImmutable.variables.map((variable) => ({ - ruleId: "sol-017", - message: variable.reason, - severity: this.getRuleSeverity("sol-017", config), - location: { - file: filePath, - startLine: variable.line, - endLine: variable.line, - }, - estimatedGasSavings: 500, - suggestedFix: { - description: `Add 'immutable' keyword to variable '${variable.name}'`, - codeSnippet: `${variable.type} immutable ${variable.name} = ;`, - documentationUrl: "https://docs.gasguard.dev/rules/sol-017", - }, - })), - ); - } - } + // Rule: sol-016 - Storage Slot Collision + if (this.isRuleEnabled("sol-016", config)) { + const collisions = detectStorageSlotCollisions(code); + if (collisions.detected) { + findings.push( + ...collisions.collisions.map((collision) => ({ + ruleId: "sol-016", + message: collision.reason, + severity: this.getRuleSeverity("sol-016", config), + location: { + file: filePath, + startLine: collision.line1, + endLine: collision.line2, + }, + suggestedFix: { + description: collisions.suggestion, + documentationUrl: "https://docs.gasguard.dev/rules/sol-016", + }, + })), + ); + } + } + + // Rule: sol-017 - Missing Immutable Variables + if (this.isRuleEnabled("sol-017", config)) { + const missingImmutable = detectMissingImmutable(code); + if (missingImmutable.detected) { + findings.push( + ...missingImmutable.variables.map((variable) => ({ + ruleId: "sol-017", + message: variable.reason, + severity: this.getRuleSeverity("sol-017", config), + location: { + file: filePath, + startLine: variable.line, + endLine: variable.line, + }, + estimatedGasSavings: 500, + suggestedFix: { + description: `Add 'immutable' keyword to variable '${variable.name}'`, + codeSnippet: `${variable.type} immutable ${variable.name} = ;`, + documentationUrl: "https://docs.gasguard.dev/rules/sol-017", + }, + })), + ); + } + } } catch (error) { errors.push({ file: filePath, diff --git a/src/analysis/stellar/upgrade-readiness/README.md b/src/analysis/stellar/upgrade-readiness/README.md new file mode 100644 index 0000000..7f54388 --- /dev/null +++ b/src/analysis/stellar/upgrade-readiness/README.md @@ -0,0 +1,318 @@ +# Soroban Upgrade Readiness Scanner + +## Overview + +The Soroban Upgrade Readiness Scanner evaluates whether a Soroban smart contract is prepared for future upgrades. It analyzes storage structures, upgrade patterns, versioning implementations, access control, and migration capabilities to provide a comprehensive readiness assessment. + +## Problem Statement + +Contracts often lack upgrade planning considerations, making it difficult or impossible to: + +- Fix bugs after deployment +- Add new features +- Migrate to improved implementations +- Preserve user state during transitions +- Rollback failed upgrades + +## Implementation Location + +``` +src/analysis/stellar/upgrade-readiness/ +├── types.ts # Type definitions +├── upgrade-readiness-analyzer.ts # Core analyzer implementation +├── upgrade-readiness-analyzer.spec.ts # Comprehensive test suite +├── index.ts # Public API exports +└── usage-example.ts # Usage demonstration +``` + +## Features + +### 1. Storage Structure Analysis + +- **Versioned Storage Detection**: Identifies if storage uses versioning patterns +- **Extensibility Assessment**: Evaluates whether storage can accommodate new fields +- **Storage Layout Classification**: Categorizes as versioned, flat, namespaced, or mixed +- **Risk Evaluation**: Assesses storage-related upgrade risks + +### 2. Upgrade Pattern Analysis + +- **Upgrade Mechanism Detection**: Identifies proxy, migrator, or version-switch patterns +- **Access Control Verification**: Ensures upgrade functions are properly protected +- **Timelock Detection**: Checks for delayed upgrade execution +- **Emergency Stop Capability**: Verifies pause/halt functionality exists + +### 3. Versioning Analysis + +- **Version Information**: Checks for exposed version constants/functions +- **Version Validation**: Detects version checking during upgrades +- **Compatibility Checks**: Identifies schema compatibility verification + +### 4. Migration Readiness + +- **Migration Path**: Verifies state transfer mechanisms exist +- **Data Migration**: Checks for data transformation logic +- **State Preservation**: Ensures critical state is maintained +- **Rollback Capability**: Verifies ability to revert failed upgrades + +## Readiness Scoring + +The scanner calculates a readiness score (0-100) based on: + +| Component | Max Points | Criteria | +| ----------------- | ---------- | ----------------------------------------------------------------------------------------- | +| Storage | 25 | Versioned (10), Extensible (10), Low Risk (5) | +| Upgrade Mechanism | 35 | Function exists (10), Access control (10), Timelock (5), Emergency stop (5), Low risk (5) | +| Versioning | 20 | Version info (8), Version check (7), Compatibility check (5) | +| Migration | 20 | Migration path (5), Data migration (5), State preservation (5), Rollback (5) | + +### Readiness Levels + +- **Excellent** (90-100): Production-ready with comprehensive upgrade support +- **Good** (70-89): Solid upgrade foundation with minor improvements needed +- **Fair** (50-69): Basic upgrade capability, significant improvements recommended +- **Poor** (30-49): Limited upgrade readiness, major work required +- **Critical** (0-29): Not upgrade-ready, high risk of data loss or bricking + +## Usage + +### Basic Usage + +```typescript +import { StellarUpgradeReadinessAnalyzer } from "./upgrade-readiness-analyzer"; + +const contractSource = `...`; // Your Soroban contract code + +const analyzer = new StellarUpgradeReadinessAnalyzer( + contractSource, + "my_contract.rs", +); + +const result = analyzer.analyze(); + +console.log(`Readiness: ${result.overallReadiness}`); +console.log(`Score: ${result.readinessScore}/100`); +console.log(`Findings: ${result.findings.length}`); +``` + +### With Custom Configuration + +```typescript +const analyzer = new StellarUpgradeReadinessAnalyzer( + contractSource, + "my_contract.rs", + { + requireVersioning: true, + requireUpgradeAuth: true, + requireTimelock: true, + requireEmergencyStop: true, + thresholds: { + excellent: 90, + good: 75, + fair: 50, + poor: 30, + }, + }, +); +``` + +### Accessing Analysis Results + +```typescript +const result = analyzer.analyze(); + +// Storage analysis +console.log(result.storageAnalysis.hasVersionedStorage); +console.log(result.storageAnalysis.storageLayout); +console.log(result.storageAnalysis.storageIssues); + +// Upgrade patterns +console.log(result.upgradePatternAnalysis.hasUpgradeFunction); +console.log(result.upgradePatternAnalysis.upgradeMechanism); +console.log(result.upgradePatternAnalysis.hasAccessControl); + +// Versioning +console.log(result.versioningAnalysis.hasVersionInfo); +console.log(result.versioningAnalysis.currentVersion); + +// Migration +console.log(result.migrationReadiness.hasMigrationPath); +console.log(result.migrationReadiness.hasRollbackCapability); + +// Findings +result.findings.forEach((finding) => { + console.log(`[${finding.severity}] ${finding.message}`); + console.log(` → ${finding.recommendation}`); +}); + +// Recommendations +result.recommendations.forEach((rec) => { + console.log(`- ${rec}`); +}); +``` + +## Findings Categories + +The scanner generates findings in these categories: + +- `storage-versioning`: Storage structure issues +- `upgrade-mechanism`: Upgrade function problems +- `access-control`: Authorization issues +- `data-migration`: Migration logic gaps +- `rollback-capability`: Rollback mechanism missing +- `version-management`: Versioning issues +- `emergency-procedures`: Emergency handling gaps +- `testing-coverage`: Testing inadequacies + +## Example Findings + +### Critical: Missing Upgrade Mechanism + +``` +Rule ID: stellar-upgrade-mechanism-missing +Severity: critical +Message: Contract lacks upgrade mechanism +Recommendation: Implement a secure upgrade function with proper access control +Impact: Contract cannot be upgraded, bugs or improvements require deployment of new contract +``` + +### High: Missing Access Control + +``` +Rule ID: stellar-upgrade-auth-missing +Severity: high +Message: Upgrade function missing access control +Recommendation: Add require_auth or role-based access control to upgrade function +Impact: Unauthorized parties can upgrade contract and compromise state +``` + +### Medium: No Version Information + +``` +Rule ID: stellar-upgrade-version-info-missing +Severity: medium +Message: Contract does not expose version information +Recommendation: Add version() function and VERSION constant +Impact: Cannot determine contract version for upgrade compatibility +``` + +## Integration with Reports + +Findings from the upgrade readiness scanner are structured to integrate seamlessly with the existing reporting system: + +```typescript +// Convert to standard Finding format +const standardFindings = result.findings.map((finding) => ({ + ruleId: finding.ruleId, + message: finding.message, + severity: finding.severity, + location: finding.location, + suggestedFix: { + description: finding.recommendation, + }, + metadata: { + category: finding.category, + impact: finding.impact, + readinessScore: result.readinessScore, + }, +})); +``` + +## Testing + +Run the comprehensive test suite: + +```bash +npm test -- src/analysis/stellar/upgrade-readiness/upgrade-readiness-analyzer.spec.ts +``` + +The test suite covers: + +- Excellent readiness contracts +- Poor readiness contracts +- Missing access control detection +- Version information detection +- Timelock mechanism detection +- Storage layout analysis +- Emergency stop capability +- Migration pattern detection +- Custom configuration handling +- Proxy pattern detection + +## Best Practices Detected + +The scanner rewards contracts that implement: + +1. **Versioned Storage Keys**: Using versioned or namespaced storage +2. **Extensible Data Structures**: Maps and Vecs instead of fixed structs +3. **Protected Upgrade Functions**: Admin-only or multi-sig authorization +4. **Timelock Mechanisms**: Delayed upgrade execution for user notification +5. **Emergency Controls**: Pause functionality for incident response +6. **Version Tracking**: Public version constants and getters +7. **Migration Functions**: State preservation and data transformation +8. **Rollback Capability**: Ability to revert failed upgrades + +## Configuration Options + +| Option | Type | Default | Description | +| -------------------- | ------- | ------- | ---------------------------------- | +| requireVersioning | boolean | true | Require versioned storage | +| requireUpgradeAuth | boolean | true | Require access control on upgrades | +| requireTimelock | boolean | false | Require timelock protection | +| requireEmergencyStop | boolean | true | Require emergency pause capability | +| thresholds.excellent | number | 90 | Score threshold for "excellent" | +| thresholds.good | number | 70 | Score threshold for "good" | +| thresholds.fair | number | 50 | Score threshold for "fair" | +| thresholds.poor | number | 30 | Score threshold for "poor" | + +## Future Enhancements + +Potential improvements: + +- AST-based analysis for more accurate detection +- Integration with Soroban's actual upgrade mechanisms +- Automated upgrade path generation +- Gas cost estimation for upgrade operations +- Historical upgrade tracking +- Multi-contract dependency analysis + +## Related Components + +- Storage Growth Analyzer: `src/analysis/stellar/storage-growth/` +- Ownership Analyzer: `src/analysis/stellar/ownership/` +- Lifecycle Analyzer: `src/analysis/stellar/lifecycle/` +- Findings System: `src/findings/` + +## API Reference + +### Classes + +#### StellarUpgradeReadinessAnalyzer + +Main analyzer class that performs upgrade readiness assessment. + +**Constructor:** + +```typescript +constructor( + source: string, + filePath: string, + config?: Partial +) +``` + +**Methods:** + +- `analyze(): UpgradeReadinessAnalysis` - Perform complete analysis + +### Key Interfaces + +- `UpgradeReadinessAnalysis` - Complete analysis result +- `StorageAnalysis` - Storage structure assessment +- `UpgradePatternAnalysis` - Upgrade mechanism evaluation +- `VersioningAnalysis` - Versioning implementation check +- `MigrationReadiness` - Migration capability assessment +- `UpgradeFinding` - Individual finding with severity and recommendations + +## License + +Part of the GasGuard project. diff --git a/src/analysis/stellar/upgrade-readiness/index.ts b/src/analysis/stellar/upgrade-readiness/index.ts new file mode 100644 index 0000000..cae79cf --- /dev/null +++ b/src/analysis/stellar/upgrade-readiness/index.ts @@ -0,0 +1,17 @@ +export { StellarUpgradeReadinessAnalyzer } from "./upgrade-readiness-analyzer"; +export type { + UpgradeReadinessAnalysis, + ReadinessLevel, + StorageAnalysis, + StorageEntry, + StorageLayoutType, + UpgradePatternAnalysis, + UpgradeMechanism, + DetectedUpgradePattern, + VersioningAnalysis, + MigrationReadiness, + UpgradeFinding, + UpgradeFindingCategory, + RiskLevel, + UpgradeReadinessConfig, +} from "./types"; diff --git a/src/analysis/stellar/upgrade-readiness/types.ts b/src/analysis/stellar/upgrade-readiness/types.ts new file mode 100644 index 0000000..9f7110c --- /dev/null +++ b/src/analysis/stellar/upgrade-readiness/types.ts @@ -0,0 +1,159 @@ +/** + * Soroban Upgrade Readiness Analyzer - Types + * + * Defines the type system for evaluating whether a Soroban contract + * is ready for future upgrades. Analyzes storage structures, upgrade + * patterns, versioning, and migration considerations. + */ + +/** Upgrade readiness assessment result */ +export interface UpgradeReadinessAnalysis { + contractName: string; + overallReadiness: ReadinessLevel; + readinessScore: number; + storageAnalysis: StorageAnalysis; + upgradePatternAnalysis: UpgradePatternAnalysis; + versioningAnalysis: VersioningAnalysis; + migrationReadiness: MigrationReadiness; + findings: UpgradeFinding[]; + recommendations: string[]; + summary: string; +} + +/** Overall readiness level */ +export type ReadinessLevel = + | "excellent" + | "good" + | "fair" + | "poor" + | "critical"; + +/** Storage structure analysis */ +export interface StorageAnalysis { + hasVersionedStorage: boolean; + storageEntries: StorageEntry[]; + storageLayout: StorageLayoutType; + hasExtensibleStorage: boolean; + storageRiskLevel: RiskLevel; + storageIssues: string[]; + storageRecommendations: string[]; +} + +/** Individual storage entry */ +export interface StorageEntry { + name: string; + type: string; + lineNumber: number; + isVersioned: boolean; + isExtensible: boolean; + riskLevel: RiskLevel; + description: string; +} + +/** Storage layout type */ +export type StorageLayoutType = + | "versioned" + | "flat" + | "namespaced" + | "mixed" + | "unknown"; + +/** Upgrade pattern analysis */ +export interface UpgradePatternAnalysis { + hasUpgradeFunction: boolean; + upgradeMechanism: UpgradeMechanism; + hasAccessControl: boolean; + hasTimelock: boolean; + hasEmergencyStop: boolean; + upgradePatterns: DetectedUpgradePattern[]; + upgradeRiskLevel: RiskLevel; + upgradeIssues: string[]; + upgradeRecommendations: string[]; +} + +/** Upgrade mechanism type */ +export type UpgradeMechanism = + | "proxy" + | "migrator" + | "version-switch" + | "none" + | "unknown"; + +/** Detected upgrade pattern */ +export interface DetectedUpgradePattern { + pattern: string; + description: string; + lineNumber: number; + isRecommended: boolean; + riskLevel: RiskLevel; +} + +/** Versioning analysis */ +export interface VersioningAnalysis { + hasVersionInfo: boolean; + currentVersion: string | null; + hasVersionCheck: boolean; + hasCompatibilityCheck: boolean; + versioningRiskLevel: RiskLevel; + versioningIssues: string[]; + versioningRecommendations: string[]; +} + +/** Migration readiness assessment */ +export interface MigrationReadiness { + hasMigrationPath: boolean; + hasDataMigration: boolean; + hasStatePreservation: boolean; + hasRollbackCapability: boolean; + migrationRiskLevel: RiskLevel; + migrationIssues: string[]; + migrationRecommendations: string[]; +} + +/** Risk level for individual components */ +export type RiskLevel = "low" | "medium" | "high" | "critical"; + +/** Individual finding about upgrade readiness */ +export interface UpgradeFinding { + ruleId: string; + message: string; + severity: "critical" | "high" | "medium" | "low" | "info"; + category: UpgradeFindingCategory; + location?: { + file: string; + startLine: number; + endLine: number; + }; + recommendation: string; + impact: string; +} + +/** Finding category */ +export type UpgradeFindingCategory = + | "storage-versioning" + | "upgrade-mechanism" + | "access-control" + | "data-migration" + | "rollback-capability" + | "version-management" + | "emergency-procedures" + | "testing-coverage"; + +/** Configuration for upgrade readiness analyzer */ +export interface UpgradeReadinessConfig { + /** Minimum required versioning */ + requireVersioning: boolean; + /** Require access control on upgrade functions */ + requireUpgradeAuth: boolean; + /** Require timelock for upgrades */ + requireTimelock: boolean; + /** Require emergency stop capability */ + requireEmergencyStop: boolean; + /** Severity thresholds */ + thresholds: { + excellent: number; + good: number; + fair: number; + poor: number; + }; +} diff --git a/src/analysis/stellar/upgrade-readiness/upgrade-readiness-analyzer.spec.ts b/src/analysis/stellar/upgrade-readiness/upgrade-readiness-analyzer.spec.ts new file mode 100644 index 0000000..80efb2d --- /dev/null +++ b/src/analysis/stellar/upgrade-readiness/upgrade-readiness-analyzer.spec.ts @@ -0,0 +1,470 @@ +import { describe, expect, it } from "@jest/globals"; +import { StellarUpgradeReadinessAnalyzer } from "./upgrade-readiness-analyzer"; + +describe("StellarUpgradeReadinessAnalyzer", () => { + it("analyzes a contract with excellent upgrade readiness", () => { + const source = ` +pub const VERSION: &str = "1.0.0"; + +#[contracttype] +pub struct TokenContract { + pub version: u32, + pub balances: Map, + pub admin: Address, + pub paused: bool, +} + +#[contractimpl] +impl TokenContract { + pub fn version() -> String { + String::from_str("1.0.0") + } + + pub fn upgrade(env: Env, new_code: Bytes) { + let admin = self.admin; + admin.require_auth(); + + if self.paused { + panic!("Cannot upgrade while paused"); + } + + // Version check + let current_version = 1; + require!(current_version >= 1, "Invalid version"); + + // Migration with state preservation + self.migrate_state(env); + } + + pub fn migrate_state(&self, env: Env) { + // Preserve state during migration + let snapshot = self.snapshot_state(); + // Transform data + self.restore_state(snapshot); + } + + pub fn pause(&mut self) { + self.admin.require_auth(); + self.paused = true; + } + + pub fn rollback(&mut self) { + self.admin.require_auth(); + // Revert to previous version + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "test.rs"); + const result = analyzer.analyze(); + + expect(result.contractName).toBe("TokenContract"); + expect(result.readinessScore).toBeGreaterThanOrEqual(70); + expect(["excellent", "good"]).toContain(result.overallReadiness); + expect(result.storageAnalysis.hasVersionedStorage).toBe(true); + expect(result.storageAnalysis.hasExtensibleStorage).toBe(true); + expect(result.upgradePatternAnalysis.hasUpgradeFunction).toBe(true); + expect(result.upgradePatternAnalysis.hasAccessControl).toBe(true); + expect(result.upgradePatternAnalysis.hasEmergencyStop).toBe(true); + expect(result.versioningAnalysis.hasVersionInfo).toBe(true); + expect(result.migrationReadiness.hasMigrationPath).toBe(true); + expect(result.migrationReadiness.hasStatePreservation).toBe(true); + expect(result.migrationReadiness.hasRollbackCapability).toBe(true); + }); + + it("detects poor upgrade readiness in basic contract", () => { + const source = ` +#[contracttype] +pub struct SimpleToken { + pub total_supply: i128, + pub balances: Map, +} + +#[contractimpl] +impl SimpleToken { + pub fn mint(&mut self, amount: i128) { + self.total_supply += amount; + } + + pub fn transfer(&mut self, to: Address, amount: i128) { + self.balances.set(to, amount); + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "simple.rs"); + const result = analyzer.analyze(); + + expect(result.contractName).toBe("SimpleToken"); + expect(result.readinessScore).toBeLessThan(50); + expect(["poor", "critical"]).toContain(result.overallReadiness); + expect(result.storageAnalysis.hasVersionedStorage).toBe(false); + expect(result.upgradePatternAnalysis.hasUpgradeFunction).toBe(false); + expect(result.versioningAnalysis.hasVersionInfo).toBe(false); + expect(result.migrationReadiness.hasMigrationPath).toBe(false); + expect(result.findings.length).toBeGreaterThan(0); + }); + + it("detects missing access control on upgrade function", () => { + const source = ` +#[contracttype] +pub struct UpgradeableContract { + pub owner: Address, + pub data: u64, +} + +#[contractimpl] +impl UpgradeableContract { + pub fn upgrade(&mut self, new_code: Bytes) { + // No access control - anyone can upgrade! + self.data = 0; + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "insecure.rs"); + const result = analyzer.analyze(); + + expect(result.upgradePatternAnalysis.hasUpgradeFunction).toBe(true); + // Note: hasAccessControl detects 'owner' keyword, but the function doesn't use require_auth + expect( + result.findings.some((f) => f.ruleId === "stellar-upgrade-auth-missing"), + ).toBe(true); + expect(result.findings.some((f) => f.severity === "critical")).toBe(true); + }); + + it("detects version information", () => { + const source = ` +pub const VERSION: &str = "2.1.0"; + +#[contracttype] +pub struct VersionedContract { + pub version: u32, +} + +#[contractimpl] +impl VersionedContract { + pub fn version() -> String { + String::from_str("2.1.0") + } + + pub fn upgrade(&mut self) { + self.owner.require_auth(); + let current = 2; + require!(current >= 2, "Version mismatch"); + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer( + source, + "versioned.rs", + ); + const result = analyzer.analyze(); + + expect(result.versioningAnalysis.hasVersionInfo).toBe(true); + expect(result.versioningAnalysis.currentVersion).toBe("2.1.0"); + expect(result.versioningAnalysis.hasVersionCheck).toBe(true); + }); + + it("detects timelock mechanism", () => { + const source = ` +#[contracttype] +pub struct TimelockContract { + pub admin: Address, + pub upgrade_delay: u64, +} + +#[contractimpl] +impl TimelockContract { + pub fn upgrade_with_delay(&mut self, delay: u64) { + self.admin.require_auth(); + let execute_time = env.ledger().timestamp() + delay; + // Timelock logic + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "timelock.rs"); + const result = analyzer.analyze(); + + expect(result.upgradePatternAnalysis.hasTimelock).toBe(true); + }); + + it("analyzes storage layout correctly", () => { + const source = ` +#[contracttype] +pub struct StorageContract { + pub version: u32, + pub users: Map, + pub configs: Map, +} + +#[contracttype] +pub struct UserInfo { + pub balance: i128, + pub nonce: u64, +} + +#[contracttype] +pub struct Config { + pub value: u64, +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "storage.rs"); + const result = analyzer.analyze(); + + expect(result.storageAnalysis.storageEntries.length).toBeGreaterThan(0); + expect(result.storageAnalysis.hasVersionedStorage).toBe(true); + expect(result.storageAnalysis.hasExtensibleStorage).toBe(true); + // Storage layout is 'mixed' because not all fields are versioned + expect(["versioned", "mixed"]).toContain( + result.storageAnalysis.storageLayout, + ); + }); + + it("generates appropriate findings", () => { + const source = ` +#[contracttype] +pub struct BasicContract { + pub value: u64, +} + +#[contractimpl] +impl BasicContract { + pub fn set_value(&mut self, val: u64) { + self.value = val; + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "basic.rs"); + const result = analyzer.analyze(); + + expect(result.findings.length).toBeGreaterThan(0); + + const findingCategories = result.findings.map((f) => f.category); + expect(findingCategories).toContain("storage-versioning"); + expect(findingCategories).toContain("upgrade-mechanism"); + expect(findingCategories).toContain("version-management"); + expect(findingCategories).toContain("data-migration"); + }); + + it("calculates readiness score correctly", () => { + const excellentContract = ` +pub const VERSION: &str = "1.0.0"; + +#[contracttype] +pub struct ExcellentContract { + pub version: u32, + pub data: Map, + pub admin: Address, + pub paused: bool, +} + +#[contractimpl] +impl ExcellentContract { + pub fn version() -> String { String::from_str("1.0.0") } + pub fn upgrade(&mut self) { + self.admin.require_auth(); + self.migrate_state(); + } + pub fn migrate_state(&self) { /* state preservation */ } + pub fn pause(&mut self) { self.paused = true; } + pub fn rollback(&mut self) { /* rollback */ } +} +`; + + const poorContract = ` +#[contracttype] +pub struct PoorContract { + pub value: u64, +} + +#[contractimpl] +impl PoorContract { + pub fn set(&mut self, v: u64) { self.value = v; } +} +`; + + const excellentAnalyzer = new StellarUpgradeReadinessAnalyzer( + excellentContract, + "excellent.rs", + ); + const excellentResult = excellentAnalyzer.analyze(); + + const poorAnalyzer = new StellarUpgradeReadinessAnalyzer( + poorContract, + "poor.rs", + ); + const poorResult = poorAnalyzer.analyze(); + + expect(excellentResult.readinessScore).toBeGreaterThan( + poorResult.readinessScore, + ); + expect(excellentResult.overallReadiness).not.toBe("critical"); + expect(poorResult.overallReadiness).toBe("critical"); + }); + + it("detects emergency stop capability", () => { + const source = ` +#[contracttype] +pub struct SafeContract { + pub admin: Address, + pub is_paused: bool, +} + +#[contractimpl] +impl SafeContract { + pub fn emergency_pause(&mut self) { + self.admin.require_auth(); + self.is_paused = true; + } + + pub fn unpause(&mut self) { + self.admin.require_auth(); + self.is_paused = false; + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "safe.rs"); + const result = analyzer.analyze(); + + expect(result.upgradePatternAnalysis.hasEmergencyStop).toBe(true); + }); + + it("detects migration patterns", () => { + const source = ` +#[contracttype] +pub struct MigratableContract { + pub admin: Address, + pub balances: Map, +} + +#[contractimpl] +impl MigratableContract { + pub fn migrate_to_v2(&mut self) { + self.admin.require_auth(); + // Transform data from v1 to v2 + self.convert_storage_format(); + } + + pub fn convert_storage_format(&self) { + // Data migration logic + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer( + source, + "migratable.rs", + ); + const result = analyzer.analyze(); + + expect(result.migrationReadiness.hasMigrationPath).toBe(true); + expect(result.migrationReadiness.hasDataMigration).toBe(true); + }); + + it("provides actionable recommendations", () => { + const source = ` +#[contracttype] +pub struct NeedsWorkContract { + pub data: u64, +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer( + source, + "needswork.rs", + ); + const result = analyzer.analyze(); + + expect(result.recommendations.length).toBeGreaterThan(0); + expect( + result.recommendations.some((r) => r.toLowerCase().includes("version")), + ).toBe(true); + expect( + result.recommendations.some((r) => r.toLowerCase().includes("upgrade")), + ).toBe(true); + expect( + result.recommendations.some((r) => r.toLowerCase().includes("storage")), + ).toBe(true); + }); + + it("generates comprehensive summary", () => { + const source = ` +#[contracttype] +pub struct SummaryContract { + pub value: u64, +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "summary.rs"); + const result = analyzer.analyze(); + + expect(result.summary).toContain("SummaryContract"); + expect(result.summary).toContain("readiness"); + expect(result.summary.length).toBeGreaterThan(20); + }); + + it("handles custom configuration", () => { + const source = ` +#[contracttype] +pub struct ConfigContract { + pub value: u64, +} + +#[contractimpl] +impl ConfigContract { + pub fn set(&mut self, v: u64) { self.value = v; } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "config.rs", { + requireVersioning: false, + requireUpgradeAuth: false, + requireTimelock: false, + requireEmergencyStop: false, + }); + + const result = analyzer.analyze(); + + // With relaxed config, should have fewer findings + expect(result.findings.length).toBeLessThan( + new StellarUpgradeReadinessAnalyzer(source, "config.rs").analyze() + .findings.length, + ); + }); + + it("detects proxy pattern", () => { + const source = ` +#[contracttype] +pub struct ProxyContract { + pub admin: Address, + pub implementation: Address, +} + +#[contractimpl] +impl ProxyContract { + pub fn upgrade_proxy(&mut self, new_impl: Address) { + self.admin.require_auth(); + self.implementation = new_impl; + } + + pub fn delegate_call(&self) { + // Proxy delegation logic + } +} +`; + + const analyzer = new StellarUpgradeReadinessAnalyzer(source, "proxy.rs"); + const result = analyzer.analyze(); + + expect(result.upgradePatternAnalysis.upgradeMechanism).toBe("proxy"); + expect( + result.upgradePatternAnalysis.upgradePatterns.length, + ).toBeGreaterThan(0); + }); +}); diff --git a/src/analysis/stellar/upgrade-readiness/upgrade-readiness-analyzer.ts b/src/analysis/stellar/upgrade-readiness/upgrade-readiness-analyzer.ts new file mode 100644 index 0000000..3e1415a --- /dev/null +++ b/src/analysis/stellar/upgrade-readiness/upgrade-readiness-analyzer.ts @@ -0,0 +1,915 @@ +/** + * Soroban Upgrade Readiness Analyzer + * + * Evaluates whether a Soroban contract is ready for future upgrades. + * Analyzes storage structures, upgrade patterns, versioning, access control, + * and migration considerations to provide a comprehensive readiness assessment. + */ + +import { + UpgradeReadinessAnalysis, + ReadinessLevel, + StorageAnalysis, + StorageEntry, + StorageLayoutType, + UpgradePatternAnalysis, + UpgradeMechanism, + DetectedUpgradePattern, + VersioningAnalysis, + MigrationReadiness, + UpgradeFinding, + RiskLevel, + UpgradeReadinessConfig, +} from "./types"; + +export class StellarUpgradeReadinessAnalyzer { + private source: string; + private filePath: string; + private config: UpgradeReadinessConfig; + private findings: UpgradeFinding[] = []; + + constructor( + source: string, + filePath: string, + config: Partial = {}, + ) { + this.source = source; + this.filePath = filePath; + this.config = { + requireVersioning: true, + requireUpgradeAuth: true, + requireTimelock: false, + requireEmergencyStop: true, + thresholds: { + excellent: 90, + good: 70, + fair: 50, + poor: 30, + }, + ...config, + }; + } + + /** + * Perform comprehensive upgrade readiness analysis + */ + analyze(): UpgradeReadinessAnalysis { + this.findings = []; + + const contractName = this.extractContractName(); + const storageAnalysis = this.analyzeStorage(); + const upgradePatternAnalysis = this.analyzeUpgradePatterns(); + const versioningAnalysis = this.analyzeVersioning(); + const migrationReadiness = this.analyzeMigrationReadiness(); + + const readinessScore = this.calculateReadinessScore( + storageAnalysis, + upgradePatternAnalysis, + versioningAnalysis, + migrationReadiness, + ); + + const overallReadiness = this.determineReadinessLevel(readinessScore); + const recommendations = this.generateRecommendations( + storageAnalysis, + upgradePatternAnalysis, + versioningAnalysis, + migrationReadiness, + ); + + const summary = this.generateSummary( + contractName, + overallReadiness, + readinessScore, + storageAnalysis, + upgradePatternAnalysis, + versioningAnalysis, + migrationReadiness, + ); + + return { + contractName, + overallReadiness, + readinessScore, + storageAnalysis, + upgradePatternAnalysis, + versioningAnalysis, + migrationReadiness, + findings: this.findings, + recommendations, + summary, + }; + } + + /** + * Analyze storage structures for upgrade readiness + */ + private analyzeStorage(): StorageAnalysis { + const storageEntries = this.extractStorageEntries(); + const hasVersionedStorage = this.detectVersionedStorage(); + const hasExtensibleStorage = this.detectExtensibleStorage(); + const storageLayout = this.determineStorageLayout(storageEntries); + const storageIssues: string[] = []; + const storageRecommendations: string[] = []; + + // Check for versioned storage + if (!hasVersionedStorage && this.config.requireVersioning) { + storageIssues.push( + "No versioned storage detected. Storage layout may break during upgrades.", + ); + this.addFinding({ + ruleId: "stellar-upgrade-storage-versioning", + message: "Contract lacks versioned storage structure", + severity: "high", + category: "storage-versioning", + recommendation: + "Use versioned storage keys or namespaced storage to support future upgrades", + impact: + "Storage data may become inaccessible or corrupted after contract upgrade", + }); + } + + // Check for extensible storage patterns + if (!hasExtensibleStorage) { + storageIssues.push( + "Storage is not extensible. Adding new fields may require full migration.", + ); + storageRecommendations.push( + "Consider using a map-based or extensible storage pattern", + ); + this.addFinding({ + ruleId: "stellar-upgrade-storage-extensible", + message: "Storage structure is not extensible", + severity: "medium", + category: "storage-versioning", + recommendation: + "Use Maps or extensible structs to allow future field additions", + impact: "Future upgrades may require complex data migration", + }); + } + + // Check for tightly packed storage that may hinder upgrades + const hasTightPacking = this.detectTightStoragePacking(); + if (hasTightPacking) { + storageRecommendations.push( + "Tightly packed storage detected. Consider leaving gaps for future fields.", + ); + } + + const storageRiskLevel = this.calculateStorageRisk( + hasVersionedStorage, + hasExtensibleStorage, + storageEntries.length, + ); + + return { + hasVersionedStorage, + storageEntries, + storageLayout, + hasExtensibleStorage, + storageRiskLevel, + storageIssues, + storageRecommendations, + }; + } + + /** + * Analyze upgrade patterns and mechanisms + */ + private analyzeUpgradePatterns(): UpgradePatternAnalysis { + const hasUpgradeFunction = this.detectUpgradeFunction(); + const upgradeMechanism = this.detectUpgradeMechanism(); + const hasAccessControl = this.detectUpgradeAccessControl(); + const hasTimelock = this.detectTimelock(); + const hasEmergencyStop = this.detectEmergencyStop(); + const upgradePatterns = this.detectUpgradePatterns(); + const upgradeIssues: string[] = []; + const upgradeRecommendations: string[] = []; + + // Check for upgrade function + if (!hasUpgradeFunction) { + upgradeIssues.push( + "No upgrade function detected. Contract cannot be upgraded safely.", + ); + this.addFinding({ + ruleId: "stellar-upgrade-mechanism-missing", + message: "Contract lacks upgrade mechanism", + severity: "critical", + category: "upgrade-mechanism", + recommendation: + "Implement a secure upgrade function with proper access control", + impact: + "Contract cannot be upgraded, bugs or improvements require deployment of new contract", + }); + } + + // Check for access control on upgrades + if ( + hasUpgradeFunction && + !hasAccessControl && + this.config.requireUpgradeAuth + ) { + upgradeIssues.push( + "Upgrade function lacks access control. Anyone can upgrade the contract.", + ); + this.addFinding({ + ruleId: "stellar-upgrade-auth-missing", + message: "Upgrade function missing access control", + severity: "critical", + category: "access-control", + recommendation: + "Add require_auth or role-based access control to upgrade function", + impact: + "Unauthorized parties can upgrade contract and compromise state", + }); + upgradeRecommendations.push( + "Add owner-only or multi-sig authorization for upgrades", + ); + } + + // Check for timelock + if (hasUpgradeFunction && !hasTimelock && this.config.requireTimelock) { + upgradeIssues.push( + "No timelock on upgrades. Changes take effect immediately.", + ); + this.addFinding({ + ruleId: "stellar-upgrade-timelock-missing", + message: "Upgrade function lacks timelock protection", + severity: "medium", + category: "upgrade-mechanism", + recommendation: "Implement timelock delay for upgrade execution", + impact: "Upgrades execute immediately without user notification period", + }); + upgradeRecommendations.push( + "Add timelock mechanism for upgrade proposals", + ); + } + + // Check for emergency stop + if (!hasEmergencyStop && this.config.requireEmergencyStop) { + upgradeIssues.push("No emergency stop mechanism detected."); + this.addFinding({ + ruleId: "stellar-upgrade-emergency-stop-missing", + message: "Contract lacks emergency stop capability", + severity: "high", + category: "emergency-procedures", + recommendation: "Implement pause/emergency stop functionality", + impact: + "Cannot halt contract operations during upgrade or security incident", + }); + upgradeRecommendations.push( + "Add emergency pause functionality to halt operations during upgrades", + ); + } + + const upgradeRiskLevel = this.calculateUpgradeRisk( + hasUpgradeFunction, + hasAccessControl, + hasTimelock, + hasEmergencyStop, + ); + + return { + hasUpgradeFunction, + upgradeMechanism, + hasAccessControl, + hasTimelock, + hasEmergencyStop, + upgradePatterns, + upgradeRiskLevel, + upgradeIssues, + upgradeRecommendations, + }; + } + + /** + * Analyze versioning implementation + */ + private analyzeVersioning(): VersioningAnalysis { + const hasVersionInfo = this.detectVersionInfo(); + const currentVersion = this.extractVersion(); + const hasVersionCheck = this.detectVersionCheck(); + const hasCompatibilityCheck = this.detectCompatibilityCheck(); + const versioningIssues: string[] = []; + const versioningRecommendations: string[] = []; + + if (!hasVersionInfo) { + versioningIssues.push("No version information found in contract."); + this.addFinding({ + ruleId: "stellar-upgrade-version-info-missing", + message: "Contract does not expose version information", + severity: "medium", + category: "version-management", + recommendation: "Add version() function and VERSION constant", + impact: "Cannot determine contract version for upgrade compatibility", + }); + versioningRecommendations.push( + "Add public version() getter and VERSION constant", + ); + } + + if (!hasVersionCheck && hasVersionInfo) { + versioningIssues.push("No version validation checks detected."); + this.addFinding({ + ruleId: "stellar-upgrade-version-check-missing", + message: "No version validation during upgrade", + severity: "high", + category: "version-management", + recommendation: + "Add version checks to prevent downgrades or incompatible upgrades", + impact: "May allow downgrades or incompatible version upgrades", + }); + versioningRecommendations.push( + "Implement version comparison logic in upgrade function", + ); + } + + if (!hasCompatibilityCheck) { + versioningRecommendations.push( + "Add compatibility checks to verify storage schema compatibility during upgrades", + ); + } + + const versioningRiskLevel = this.calculateVersioningRisk( + hasVersionInfo, + hasVersionCheck, + hasCompatibilityCheck, + ); + + return { + hasVersionInfo, + currentVersion, + hasVersionCheck, + hasCompatibilityCheck, + versioningRiskLevel, + versioningIssues, + versioningRecommendations, + }; + } + + /** + * Analyze migration readiness + */ + private analyzeMigrationReadiness(): MigrationReadiness { + const hasMigrationPath = this.detectMigrationPath(); + const hasDataMigration = this.detectDataMigration(); + const hasStatePreservation = this.detectStatePreservation(); + const hasRollbackCapability = this.detectRollbackCapability(); + const migrationIssues: string[] = []; + const migrationRecommendations: string[] = []; + + if (!hasMigrationPath) { + migrationIssues.push("No migration path defined for state transfer."); + this.addFinding({ + ruleId: "stellar-upgrade-migration-path-missing", + message: "Contract lacks migration path for upgrades", + severity: "high", + category: "data-migration", + recommendation: + "Implement migration function to transfer state to new version", + impact: "State data may be lost during contract upgrade", + }); + migrationRecommendations.push( + "Create migration function to preserve state across upgrades", + ); + } + + if (!hasDataMigration && hasMigrationPath) { + migrationIssues.push( + "Migration path exists but no data migration logic detected.", + ); + this.addFinding({ + ruleId: "stellar-upgrade-data-migration-missing", + message: "No data migration logic found", + severity: "high", + category: "data-migration", + recommendation: + "Implement data transformation logic for storage schema changes", + impact: "Data format changes may cause incompatibility after upgrade", + }); + } + + if (!hasStatePreservation) { + migrationIssues.push("No state preservation mechanism detected."); + this.addFinding({ + ruleId: "stellar-upgrade-state-preservation-missing", + message: "No state preservation during upgrade", + severity: "critical", + category: "data-migration", + recommendation: + "Ensure critical state is preserved or migrated during upgrade", + impact: "All contract state may be lost during upgrade", + }); + migrationRecommendations.push( + "Implement state snapshot and restore mechanism", + ); + } + + if (!hasRollbackCapability) { + migrationIssues.push("No rollback capability detected."); + this.addFinding({ + ruleId: "stellar-upgrade-rollback-missing", + message: "Contract cannot rollback failed upgrades", + severity: "medium", + category: "rollback-capability", + recommendation: "Implement rollback mechanism for failed upgrades", + impact: "Failed upgrades may leave contract in broken state", + }); + migrationRecommendations.push( + "Add rollback function to revert to previous version on failure", + ); + } + + const migrationRiskLevel = this.calculateMigrationRisk( + hasMigrationPath, + hasDataMigration, + hasStatePreservation, + hasRollbackCapability, + ); + + return { + hasMigrationPath, + hasDataMigration, + hasStatePreservation, + hasRollbackCapability, + migrationRiskLevel, + migrationIssues, + migrationRecommendations, + }; + } + + // ==================== Helper Methods ==================== + + private extractContractName(): string { + const match = this.source.match(/pub struct (\w+)/); + return match ? match[1] : "UnknownContract"; + } + + private extractStorageEntries(): StorageEntry[] { + const entries: StorageEntry[] = []; + const structRegex = /pub struct \w+ \{([^}]+)\}/g; + let structMatch; + + while ((structMatch = structRegex.exec(this.source)) !== null) { + const structBody = structMatch[1]; + const fieldRegex = /pub\s+(\w+)\s*:\s*([^,\n]+)/g; + let fieldMatch; + + while ((fieldMatch = fieldRegex.exec(structBody)) !== null) { + const name = fieldMatch[1]; + const type = fieldMatch[2].trim(); + const lineNumber = this.getLineNumber( + structMatch.index + fieldMatch.index, + ); + + entries.push({ + name, + type, + lineNumber, + isVersioned: this.isFieldVersioned(name, type), + isExtensible: this.isFieldExtensible(type), + riskLevel: this.assessFieldRisk(type), + description: `Storage field: ${name}`, + }); + } + } + + return entries; + } + + private detectVersionedStorage(): boolean { + return ( + this.source.includes("version") || + this.source.includes("VERSION") || + this.source.includes("schema_version") || + /storage_key.*v\d+/i.test(this.source) || + /namespaced/i.test(this.source) + ); + } + + private detectExtensibleStorage(): boolean { + return ( + this.source.includes("Map<") || + this.source.includes("HashMap<") || + this.source.includes("Vec<") || + this.source.includes("extend") || + this.source.includes("dynamic") + ); + } + + private determineStorageLayout(entries: StorageEntry[]): StorageLayoutType { + const versionedCount = entries.filter((e) => e.isVersioned).length; + const totalEntries = entries.length; + + if (totalEntries === 0) return "unknown"; + if (versionedCount === totalEntries && versionedCount > 0) + return "versioned"; + if (versionedCount > 0 && versionedCount < totalEntries) return "mixed"; + if (versionedCount > 0 && this.detectVersionedStorage()) return "versioned"; + + // Check for namespaced pattern + if (this.source.includes("namespace") || this.source.includes("prefix")) { + return "namespaced"; + } + + return "flat"; + } + + private detectTightStoragePacking(): boolean { + // Look for multiple small types declared consecutively + const smallTypes = ["u8", "u16", "u32", "bool"]; + let consecutiveSmall = 0; + + const lines = this.source.split("\n"); + for (const line of lines) { + const hasSmallType = smallTypes.some((t) => line.includes(`: ${t}`)); + if (hasSmallType) { + consecutiveSmall++; + if (consecutiveSmall >= 3) return true; + } else { + consecutiveSmall = 0; + } + } + + return false; + } + + private detectUpgradeFunction(): boolean { + return ( + /fn\s+upgrade/i.test(this.source) || + /fn\s+migrate/i.test(this.source) || + /fn\s+update_implementation/i.test(this.source) || + /fn\s+set_code/i.test(this.source) + ); + } + + private detectUpgradeMechanism(): UpgradeMechanism { + if (/proxy|forwarder/i.test(this.source)) return "proxy"; + if (/migrat/i.test(this.source)) return "migrator"; + if (/version.*switch|switch.*version/i.test(this.source)) + return "version-switch"; + if (this.detectUpgradeFunction()) return "unknown"; + return "none"; + } + + private detectUpgradeAccessControl(): boolean { + return ( + this.source.includes("require_auth(") || + this.source.includes("require_auth (") || + (this.source.includes("owner") && + /fn\s+upgrade/i.test(this.source) && + /owner.*require_auth|require_auth.*owner/i.test(this.source)) || + (this.source.includes("admin") && + /fn\s+upgrade/i.test(this.source) && + /admin.*require_auth|require_auth.*admin/i.test(this.source)) || + this.source.includes("only_owner") || + this.source.includes("only_admin") + ); + } + + private detectTimelock(): boolean { + return ( + /timelock|time_lock/i.test(this.source) || + /delay.*upgrade/i.test(this.source) || + /upgrade.*delay/i.test(this.source) || + /ledger.*timestamp.*\+/i.test(this.source) + ); + } + + private detectEmergencyStop(): boolean { + return ( + /pause|emergency/i.test(this.source) || + /halt|stop/i.test(this.source) || + /circuit.*breaker/i.test(this.source) || + /is_paused|paused/i.test(this.source) + ); + } + + private detectUpgradePatterns(): DetectedUpgradePattern[] { + const patterns: DetectedUpgradePattern[] = []; + + // Check for proxy pattern + if (/proxy|delegate/i.test(this.source)) { + patterns.push({ + pattern: "proxy", + description: "Proxy pattern detected for upgrade mechanism", + lineNumber: this.findPatternLine(/proxy|delegate/), + isRecommended: true, + riskLevel: "low", + }); + } + + // Check for migrator pattern + if (/migrat/i.test(this.source)) { + patterns.push({ + pattern: "migrator", + description: "Migration pattern detected", + lineNumber: this.findPatternLine(/migrat/), + isRecommended: true, + riskLevel: "low", + }); + } + + // Check for admin-controlled upgrade + if (this.detectUpgradeFunction() && this.detectUpgradeAccessControl()) { + patterns.push({ + pattern: "admin-controlled", + description: "Admin-controlled upgrade pattern", + lineNumber: this.findPatternLine(/fn\s+upgrade/i), + isRecommended: true, + riskLevel: "medium", + }); + } + + return patterns; + } + + private detectVersionInfo(): boolean { + return ( + /VERSION|version/i.test(this.source) || + /fn\s+version/i.test(this.source) || + /const.*VERSION/i.test(this.source) + ); + } + + private extractVersion(): string | null { + const versionMatch = this.source.match(/VERSION\s*=\s*"([^"]+)"/); + if (versionMatch) return versionMatch[1]; + + const versionFnMatch = this.source.match( + /fn\s+version\s*\(\s*\)\s*->\s*String\s*\{[^}]*String::from_str\("([^"]+)"/, + ); + if (versionFnMatch) return versionFnMatch[1]; + + return null; + } + + private detectVersionCheck(): boolean { + return ( + /version.*check|check.*version/i.test(this.source) || + /version.*>=|version.*<=|version.*==/i.test(this.source) || + /require.*version/i.test(this.source) + ); + } + + private detectCompatibilityCheck(): boolean { + return ( + /compatib/i.test(this.source) || + /schema.*check|check.*schema/i.test(this.source) || + /validate.*storage/i.test(this.source) + ); + } + + private detectMigrationPath(): boolean { + return ( + /fn\s+migrat/i.test(this.source) || + /migrate_state|transfer_state/i.test(this.source) || + /migration/i.test(this.source) + ); + } + + private detectDataMigration(): boolean { + return ( + /transform.*data|data.*transform/i.test(this.source) || + /convert.*storage|storage.*convert/i.test(this.source) || + /map.*old.*new/i.test(this.source) + ); + } + + private detectStatePreservation(): boolean { + return ( + /preserve.*state|state.*preserve/i.test(this.source) || + /snapshot|restore/i.test(this.source) || + /save.*state|state.*save/i.test(this.source) + ); + } + + private detectRollbackCapability(): boolean { + return ( + /rollback|roll_back/i.test(this.source) || + /revert.*upgrade|upgrade.*revert/i.test(this.source) || + /fallback.*version/i.test(this.source) + ); + } + + private isFieldVersioned(name: string, type: string): boolean { + return ( + name.includes("version") || + type.includes("version") || + name.includes("v1") || + name.includes("v2") + ); + } + + private isFieldExtensible(type: string): boolean { + return ( + type.includes("Map<") || + type.includes("Vec<") || + type.includes("HashMap<") + ); + } + + private assessFieldRisk(type: string): RiskLevel { + if (type.includes("Map") || type.includes("Vec")) return "low"; + if (type.includes("Address") || type.includes("Balance")) return "medium"; + return "low"; + } + + private calculateStorageRisk( + hasVersioned: boolean, + hasExtensible: boolean, + entryCount: number, + ): RiskLevel { + let risk = 0; + if (!hasVersioned) risk += 3; + if (!hasExtensible) risk += 2; + if (entryCount > 10) risk += 1; + + if (risk >= 5) return "critical"; + if (risk >= 3) return "high"; + if (risk >= 2) return "medium"; + return "low"; + } + + private calculateUpgradeRisk( + hasUpgrade: boolean, + hasAuth: boolean, + hasTimelock: boolean, + hasEmergency: boolean, + ): RiskLevel { + let risk = 0; + if (!hasUpgrade) risk += 4; + if (hasUpgrade && !hasAuth) risk += 4; + if (!hasTimelock) risk += 1; + if (!hasEmergency) risk += 2; + + if (risk >= 7) return "critical"; + if (risk >= 5) return "high"; + if (risk >= 3) return "medium"; + return "low"; + } + + private calculateVersioningRisk( + hasVersion: boolean, + hasCheck: boolean, + hasCompat: boolean, + ): RiskLevel { + let risk = 0; + if (!hasVersion) risk += 3; + if (!hasCheck) risk += 2; + if (!hasCompat) risk += 1; + + if (risk >= 5) return "critical"; + if (risk >= 3) return "high"; + if (risk >= 2) return "medium"; + return "low"; + } + + private calculateMigrationRisk( + hasPath: boolean, + hasData: boolean, + hasPreserve: boolean, + hasRollback: boolean, + ): RiskLevel { + let risk = 0; + if (!hasPath) risk += 2; + if (!hasData) risk += 2; + if (!hasPreserve) risk += 3; + if (!hasRollback) risk += 1; + + if (risk >= 6) return "critical"; + if (risk >= 4) return "high"; + if (risk >= 2) return "medium"; + return "low"; + } + + private calculateReadinessScore( + storage: StorageAnalysis, + upgrade: UpgradePatternAnalysis, + versioning: VersioningAnalysis, + migration: MigrationReadiness, + ): number { + let score = 0; + const maxScore = 100; + + // Storage: 25 points + if (storage.hasVersionedStorage) score += 10; + if (storage.hasExtensibleStorage) score += 10; + if (storage.storageRiskLevel === "low") score += 5; + + // Upgrade: 35 points + if (upgrade.hasUpgradeFunction) score += 10; + if (upgrade.hasAccessControl) score += 10; + if (upgrade.hasTimelock) score += 5; + if (upgrade.hasEmergencyStop) score += 5; + if (upgrade.upgradeRiskLevel === "low") score += 5; + + // Versioning: 20 points + if (versioning.hasVersionInfo) score += 8; + if (versioning.hasVersionCheck) score += 7; + if (versioning.hasCompatibilityCheck) score += 5; + + // Migration: 20 points + if (migration.hasMigrationPath) score += 5; + if (migration.hasDataMigration) score += 5; + if (migration.hasStatePreservation) score += 5; + if (migration.hasRollbackCapability) score += 5; + + return Math.min(Math.round((score / maxScore) * 100), 100); + } + + private determineReadinessLevel(score: number): ReadinessLevel { + if (score >= this.config.thresholds.excellent) return "excellent"; + if (score >= this.config.thresholds.good) return "good"; + if (score >= this.config.thresholds.fair) return "fair"; + if (score >= this.config.thresholds.poor) return "poor"; + return "critical"; + } + + private generateRecommendations( + storage: StorageAnalysis, + upgrade: UpgradePatternAnalysis, + versioning: VersioningAnalysis, + migration: MigrationReadiness, + ): string[] { + const recommendations: string[] = []; + + recommendations.push(...storage.storageRecommendations); + recommendations.push(...upgrade.upgradeRecommendations); + recommendations.push(...versioning.versioningRecommendations); + recommendations.push(...migration.migrationRecommendations); + + if (recommendations.length === 0) { + recommendations.push( + "Contract demonstrates good upgrade readiness practices", + ); + } + + return recommendations; + } + + private generateSummary( + contractName: string, + readiness: ReadinessLevel, + score: number, + storage: StorageAnalysis, + upgrade: UpgradePatternAnalysis, + versioning: VersioningAnalysis, + migration: MigrationReadiness, + ): string { + const parts: string[] = []; + parts.push( + `Contract "${contractName}" upgrade readiness: ${readiness} (${score}/100)`, + ); + + if (storage.hasVersionedStorage) { + parts.push("✓ Versioned storage"); + } else { + parts.push("✗ No versioned storage"); + } + + if (upgrade.hasUpgradeFunction) { + parts.push(`✓ Upgrade mechanism (${upgrade.upgradeMechanism})`); + } else { + parts.push("✗ No upgrade mechanism"); + } + + if (versioning.hasVersionInfo) { + parts.push( + `✓ Version tracking (${versioning.currentVersion || "detected"})`, + ); + } else { + parts.push("✗ No version tracking"); + } + + if (migration.hasStatePreservation) { + parts.push("✓ State preservation"); + } else { + parts.push("✗ No state preservation"); + } + + return parts.join(". "); + } + + private addFinding(finding: Omit): void { + this.findings.push({ + ...finding, + location: { + file: this.filePath, + startLine: 1, + endLine: 1, + }, + }); + } + + private getLineNumber(offset: number): number { + const before = this.source.substring(0, offset); + return (before.match(/\n/g) || []).length + 1; + } + + private findPatternLine(pattern: RegExp): number { + const match = pattern.exec(this.source); + if (!match) return 1; + return this.getLineNumber(match.index); + } +} diff --git a/src/analysis/stellar/upgrade-readiness/usage-example.ts b/src/analysis/stellar/upgrade-readiness/usage-example.ts new file mode 100644 index 0000000..b624bd3 --- /dev/null +++ b/src/analysis/stellar/upgrade-readiness/usage-example.ts @@ -0,0 +1,163 @@ +/** + * Soroban Upgrade Readiness Scanner - Usage Example + * + * This example demonstrates how to use the upgrade readiness scanner + * and integrate its findings into reports. + */ + +import { StellarUpgradeReadinessAnalyzer } from "./upgrade-readiness-analyzer"; + +// Example 1: Contract with excellent upgrade readiness +const wellDesignedContract = ` +pub const VERSION: &str = "1.0.0"; + +#[contracttype] +pub struct TokenContract { + pub version: u32, + pub balances: Map, + pub admin: Address, + pub paused: bool, +} + +#[contractimpl] +impl TokenContract { + pub fn version() -> String { + String::from_str("1.0.0") + } + + pub fn upgrade(env: Env, new_code: Bytes) { + let admin = self.admin; + admin.require_auth(); + + if self.paused { + panic!("Cannot upgrade while paused"); + } + + // Version check + let current_version = 1; + require!(current_version >= 1, "Invalid version"); + + // Migration with state preservation + self.migrate_state(env); + } + + pub fn migrate_state(&self, env: Env) { + // Preserve state during migration + let snapshot = self.snapshot_state(); + self.restore_state(snapshot); + } + + pub fn pause(&mut self) { + self.admin.require_auth(); + self.paused = true; + } + + pub fn rollback(&mut self) { + self.admin.require_auth(); + } +} +`; + +// Example 2: Contract lacking upgrade considerations +const basicContract = ` +#[contracttype] +pub struct SimpleToken { + pub total_supply: i128, + pub balances: Map, +} + +#[contractimpl] +impl SimpleToken { + pub fn mint(&mut self, amount: i128) { + self.total_supply += amount; + } + + pub fn transfer(&mut self, to: Address, amount: i128) { + self.balances.set(to, amount); + } +} +`; + +function runAnalysis() { + console.log("=== Soroban Upgrade Readiness Scanner ===\n"); + + // Analyze well-designed contract + console.log("Example 1: Well-Designed Contract"); + console.log("-----------------------------------"); + const analyzer1 = new StellarUpgradeReadinessAnalyzer( + wellDesignedContract, + "token_contract.rs", + ); + const result1 = analyzer1.analyze(); + + console.log(`Contract: ${result1.contractName}`); + console.log(`Readiness Level: ${result1.overallReadiness}`); + console.log(`Readiness Score: ${result1.readinessScore}/100`); + console.log(`\nKey Features:`); + console.log( + ` ✓ Versioned Storage: ${result1.storageAnalysis.hasVersionedStorage}`, + ); + console.log( + ` ✓ Upgrade Mechanism: ${result1.upgradePatternAnalysis.hasUpgradeFunction}`, + ); + console.log( + ` ✓ Access Control: ${result1.upgradePatternAnalysis.hasAccessControl}`, + ); + console.log( + ` ✓ Emergency Stop: ${result1.upgradePatternAnalysis.hasEmergencyStop}`, + ); + console.log(` ✓ Version Info: ${result1.versioningAnalysis.hasVersionInfo}`); + console.log( + ` ✓ Migration Path: ${result1.migrationReadiness.hasMigrationPath}`, + ); + console.log(`\nFindings: ${result1.findings.length}`); + console.log(`Recommendations: ${result1.recommendations.length}`); + console.log(`\nSummary:`); + console.log(result1.summary); + + console.log("\n\n"); + + // Analyze basic contract + console.log("Example 2: Basic Contract (Needs Improvement)"); + console.log("-----------------------------------------------"); + const analyzer2 = new StellarUpgradeReadinessAnalyzer( + basicContract, + "simple_token.rs", + ); + const result2 = analyzer2.analyze(); + + console.log(`Contract: ${result2.contractName}`); + console.log(`Readiness Level: ${result2.overallReadiness}`); + console.log(`Readiness Score: ${result2.readinessScore}/100`); + console.log(`\nKey Features:`); + console.log( + ` ✗ Versioned Storage: ${result2.storageAnalysis.hasVersionedStorage}`, + ); + console.log( + ` ✗ Upgrade Mechanism: ${result2.upgradePatternAnalysis.hasUpgradeFunction}`, + ); + console.log(` ✗ Version Info: ${result2.versioningAnalysis.hasVersionInfo}`); + console.log( + ` ✗ Migration Path: ${result2.migrationReadiness.hasMigrationPath}`, + ); + console.log(`\nFindings: ${result2.findings.length}`); + + console.log("\nCritical Findings:"); + result2.findings + .filter((f) => f.severity === "critical" || f.severity === "high") + .forEach((f) => { + console.log(` [${f.severity.toUpperCase()}] ${f.message}`); + console.log(` → ${f.recommendation}`); + }); + + console.log(`\nRecommendations:`); + result2.recommendations.forEach((rec, i) => { + console.log(` ${i + 1}. ${rec}`); + }); + + console.log(`\nSummary:`); + console.log(result2.summary); +} + +// Run the analysis +runAnalysis();