Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

210 changes: 210 additions & 0 deletions SNAPSHOT_TOOL_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Soroban Contract State Snapshot Tool - Implementation Summary

## Overview

Successfully implemented a comprehensive Soroban Contract State Snapshot Tool that enables developers to capture, export, and restore Soroban contract state snapshots for reproducible audit and debugging purposes.

## Implementation Details

### Files Created/Modified

#### 1. **snapshot-exporter.ts** (Modified)

- **Purpose**: Captures and exports Soroban contract state snapshots
- **Key Features**:
- Export complete contract state snapshots via Soroban RPC
- Retrieve storage entries (instance, persistent, temporary)
- Capture contract metadata (network, ledger, timestamp)
- Validate snapshots before export or restoration
- Export/Import snapshots as JSON
- Proper Stellar SDK integration with `rpc.Server` and XDR encoding

#### 2. **snapshot-restorer.ts** (Created)

- **Purpose**: Restores contract state from snapshots
- **Key Features**:
- Restore storage entries from snapshot files
- Validate snapshots before restoration
- Network passphrase verification to prevent cross-network restoration
- Preview restoration without applying changes (dry run support)
- Configurable overwrite/skip behavior for existing entries
- Detailed restoration results with success/failure tracking

#### 3. **types.ts** (Pre-existing, utilized)

- Comprehensive TypeScript interfaces for:
- `StorageEntry` - Individual storage entries
- `ContractMetadata` - Contract and network metadata
- `ContractStateSnapshot` - Complete snapshot structure
- `SnapshotExportConfig` / `SnapshotRestoreConfig` - Configuration options
- `SnapshotExportResult` / `SnapshotRestoreResult` - Operation results
- `SnapshotValidationResult` - Validation feedback

#### 4. **index.ts** (Created)

- Module exports for easy importing
- Re-exports all public APIs and types

#### 5. **Tests** (Created)

- `snapshot-exporter.spec.ts` - 16 comprehensive tests
- `snapshot-restorer.spec.ts` - 10 comprehensive tests
- **Total: 26 tests, all passing ✓**

## Key Technical Implementation

### Stellar SDK Integration

- Uses `@stellar/stellar-sdk` v15.0.1
- Proper RPC server initialization with `rpc.Server`
- XDR encoding/decoding for storage keys and values
- Ledger key construction using `xdr.LedgerKeyContractData`
- Storage type mapping to `rpc.Durability` enum

### Storage Key Handling

```typescript
// Creates proper XDR-encoded ledger keys
const contractData = new xdr.LedgerKeyContractData({
contract: contractAddress.toScAddress(),
key: xdr.ScVal.scvSymbol(key),
durability: rpc.Durability.Persistent,
});
```

### Snapshot Validation

- Contract ID verification
- Network passphrase validation
- Ledger sequence validation
- Storage entry integrity checks
- Storage type validation
- Warnings for empty or large snapshots

## Usage Examples

### Exporting a Snapshot

```typescript
import { SorobanSnapshotExporter } from "./src/state/snapshots/stellar";

const exporter = new SorobanSnapshotExporter(
"https://soroban-testnet.stellar.org",
);

const result = await exporter.exportSnapshot(
"CDLZVWRQK6QZL4QF5VQKQZL4QF5VQKQZL4QF5VQKQZL4QF5VQKQZL4QF",
);

if (result.success) {
// Save snapshot to file
const json = exporter.exportToJson(result.snapshot!);
require("fs").writeFileSync("snapshot.json", json);
}
```

### Restoring a Snapshot

```typescript
import { SorobanSnapshotRestorer } from "./src/state/snapshots/stellar";
import { Keypair } from "@stellar/stellar-sdk";

const restorer = new SorobanSnapshotRestorer(
"https://soroban-testnet.stellar.org",
);
const snapshot = JSON.parse(
require("fs").readFileSync("snapshot.json", "utf8"),
);
const signer = Keypair.fromSecret("SECRET_KEY_HERE");

// Preview first
const preview = await restorer.previewRestore(snapshot);
console.log(`Will restore ${preview.newEntries} new entries`);

// Restore
const result = await restorer.restoreSnapshot(snapshot, signer, {
dryRun: false,
overwriteExisting: false,
});

console.log(`Restored ${result.entriesRestored} entries`);
```

## Test Coverage

### Exporter Tests (16 tests)

- ✓ Constructor with default and custom configs
- ✓ Snapshot validation (valid/invalid cases)
- ✓ Missing contract ID detection
- ✓ Missing network passphrase detection
- ✓ Invalid ledger sequence detection
- ✓ Empty storage entries warning
- ✓ Large snapshot warning (>1000 entries)
- ✓ Invalid storage type detection
- ✓ JSON export/import roundtrip
- ✓ Data preservation during import
- ✓ Error handling for invalid contract IDs
- ✓ Duration measurement

### Restorer Tests (10 tests)

- ✓ Constructor with default and custom configs
- ✓ Validation failure for invalid snapshots
- ✓ Restoration duration measurement
- ✓ Network mismatch detection
- ✓ Preview without applying changes
- ✓ Empty snapshot handling
- ✓ Entry categorization (existing vs new)
- ✓ Dry run configuration
- ✓ Skip validation configuration

## Acceptance Criteria - All Met ✓

| Criteria | Status | Notes |
| ---------------------------- | ------ | ----------------------------------------------------- |
| Export contract state | ✅ | Full implementation with metadata and storage entries |
| Support snapshot restoration | ✅ | Complete restoration with validation and preview |
| Snapshot tool implemented | ✅ | Production-ready with comprehensive error handling |
| Builds successfully | ✅ | TypeScript compilation passes |
| Passes lint | ✅ | ESLint passes with zero errors |
| Passes tests | ✅ | 26/26 tests passing |
| CI/CD ready | ✅ | Follows project patterns and conventions |

## Quality Metrics

- **Type Safety**: 100% TypeScript with strict type checking
- **Error Handling**: Comprehensive try-catch blocks with detailed error messages
- **Validation**: Multi-layer validation (schema, network, data integrity)
- **Test Coverage**: 26 unit tests covering happy paths and edge cases
- **Documentation**: Inline JSDoc comments for all public APIs
- **Code Quality**: Passes ESLint with project standards

## Integration Points

### CI/CD Compatibility

- Follows existing project structure in `src/state/snapshots/stellar/`
- Uses project's TypeScript configuration
- Compatible with Jest test framework
- Follows ESLint rules and patterns
- No breaking changes to existing code

### Dependencies

- `@stellar/stellar-sdk@^15.0.1` (already in package.json)
- Standard TypeScript/Node.js runtime
- No additional dependencies required

## Future Enhancements (Optional)

1. Storage key discovery automation via contract introspection
2. Batch export/import for multiple contracts
3. Snapshot diffing and comparison tools
4. Snapshot versioning and migration support
5. CLI tool for easy snapshot management
6. Snapshot encryption for sensitive state data

## Conclusion

The Soroban Contract State Snapshot Tool is fully implemented, tested, and ready for production use. It provides developers with reliable tools for capturing reproducible contract state snapshots essential for auditing, debugging, and development workflows.
16 changes: 8 additions & 8 deletions apps/api-service/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ const config: HardhatUserConfig = {
chainId: 1337,
mining: {
auto: true,
interval: 1000
interval: 1000,
},
accounts: {
count: 20,
accountsBalance: "10000000000000000000000" // 10,000 ETH
}
accountsBalance: "10000000000000000000000", // 10,000 ETH
},
},
localhost: {
url: "http://127.0.0.1:8545",
chainId: 1337
}
chainId: 1337,
},
},
mocha: {
timeout: 40000
}
timeout: 40000,
},
};

export default config;
export default config;
24 changes: 12 additions & 12 deletions apps/api-service/ormconfig.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { DataSource } from 'typeorm';
import { DataSource } from "typeorm";

export default new DataSource({
type: 'postgres',
host: process.env.DATABASE_HOST || 'localhost',
port: parseInt(process.env.DATABASE_PORT || '5432', 10),
username: process.env.DATABASE_USERNAME || 'postgres',
password: process.env.DATABASE_PASSWORD || 'postgres',
database: process.env.DATABASE_NAME || 'gasguard',
type: "postgres",
host: process.env.DATABASE_HOST || "localhost",
port: parseInt(process.env.DATABASE_PORT || "5432", 10),
username: process.env.DATABASE_USERNAME || "postgres",
password: process.env.DATABASE_PASSWORD || "postgres",
database: process.env.DATABASE_NAME || "gasguard",
synchronize: false,
logging: process.env.DATABASE_LOGGING === 'true',
entities: ['src/database/entities/**/*.entity{.ts,.js}'],
migrations: ['src/database/migrations/**/*{.ts,.js}'],
subscribers: ['src/database/subscribers/**/*{.ts,.js}'],
});
logging: process.env.DATABASE_LOGGING === "true",
entities: ["src/database/entities/**/*.entity{.ts,.js}"],
migrations: ["src/database/migrations/**/*{.ts,.js}"],
subscribers: ["src/database/subscribers/**/*{.ts,.js}"],
});
2 changes: 1 addition & 1 deletion apps/api-service/src/@nestjs/common.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module '@nestjs/common' {
declare module "@nestjs/common" {
export function Injectable(): ClassDecorator;
export function Module(options: any): ClassDecorator;
export function Controller(path?: string): ClassDecorator;
Expand Down
2 changes: 1 addition & 1 deletion apps/api-service/src/@nestjs/config.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module '@nestjs/config' {
declare module "@nestjs/config" {
export function ConfigModule(options?: any): ClassDecorator;
export namespace ConfigModule {
export function forRoot(options?: any): any;
Expand Down
22 changes: 11 additions & 11 deletions apps/api-service/src/@nestjs/schedule.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
declare module '@nestjs/schedule' {
declare module "@nestjs/schedule" {
export function Cron(expression: string): MethodDecorator;
export class ScheduleModule {
static forRoot(): any;
}
export enum CronExpression {
EVERY_SECOND = '* * * * * *',
EVERY_5_SECONDS = '*/5 * * * * *',
EVERY_10_SECONDS = '*/10 * * * * *',
EVERY_30_SECONDS = '*/30 * * * * *',
EVERY_MINUTE = '0 * * * * *',
EVERY_5_MINUTES = '0 */5 * * * *',
EVERY_10_MINUTES = '0 */10 * * * *',
EVERY_30_MINUTES = '0 */30 * * * *',
EVERY_HOUR = '0 0 * * * *',
EVERY_DAY = '0 0 0 * * *',
EVERY_SECOND = "* * * * * *",
EVERY_5_SECONDS = "*/5 * * * * *",
EVERY_10_SECONDS = "*/10 * * * * *",
EVERY_30_SECONDS = "*/30 * * * * *",
EVERY_MINUTE = "0 * * * * *",
EVERY_5_MINUTES = "0 */5 * * * *",
EVERY_10_MINUTES = "0 */10 * * * *",
EVERY_30_MINUTES = "0 */30 * * * *",
EVERY_HOUR = "0 0 * * * *",
EVERY_DAY = "0 0 0 * * *",
}
}
2 changes: 1 addition & 1 deletion apps/api-service/src/@nestjs/swagger.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module '@nestjs/swagger' {
declare module "@nestjs/swagger" {
export function ApiTags(...tags: string[]): ClassDecorator;
export function ApiOperation(options: any): MethodDecorator;
export function ApiResponse(options: any): MethodDecorator;
Expand Down
3 changes: 1 addition & 2 deletions apps/api-service/src/@nestjs/testing.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module '@nestjs/testing' {
declare module "@nestjs/testing" {
export class Test {
static createTestingModule(metadata: any): {
compile(): Promise<TestingModule>;
Expand All @@ -9,4 +9,3 @@ declare module '@nestjs/testing' {
get<T>(type: any): T;
}
}

4 changes: 2 additions & 2 deletions apps/api-service/src/@nestjs/typeorm.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
declare module '@nestjs/typeorm' {
declare module "@nestjs/typeorm" {
export function getRepositoryToken(entity: any): string;
export class TypeOrmModule {
static forFeature(entities: any[]): any;
}
export function InjectRepository(entity: any): ParameterDecorator;
}
}
Loading
Loading