harb/VERSION_VALIDATION.md
johba 6cbb1781ce tax rate, version and compose (#70)
resolves #67

Co-authored-by: johba <johba@harb.eth>
Reviewed-on: https://codeberg.org/johba/harb/pulls/70
2025-10-07 19:26:08 +02:00

8.4 KiB

Version Validation System

Overview

The Kraiken protocol now includes a version validation system that ensures all stack components (contract, indexer, frontend) are synchronized and compatible. This prevents subtle data corruption bugs that arise from version mismatches.

Architecture

┌─────────────────────────────────────┐
│  Kraiken.sol                        │
│  uint256 public constant VERSION=1  │  ← Source of Truth
└──────────────┬──────────────────────┘
               │ read at startup
               ▼
┌─────────────────────────────────────┐
│  Ponder Indexer                     │
│  • Reads contract.VERSION()         │
│  • Validates against                │
│    COMPATIBLE_CONTRACT_VERSIONS     │
│  • FAILS HARD if mismatch           │
└──────────────┬──────────────────────┘
               │ GraphQL
               ▼
┌─────────────────────────────────────┐
│  Frontend (web-app)                 │
│  • Checks KRAIKEN_LIB_VERSION       │
│  • Shows warning if issues          │
│  • Continues operation              │
└─────────────────────────────────────┘

Components

1. Contract Version Constant

File: onchain/src/Kraiken.sol

contract Kraiken is ERC20, ERC20Permit {
    /**
     * @notice Protocol version for data structure compatibility.
     * Increment when making breaking changes to TAX_RATES, events, or core data structures.
     */
    uint256 public constant VERSION = 1;
    
    // ...
}

Key properties:

  • public constant = free to read, immutable
  • Forces contract redeployment on breaking changes
  • Single source of truth for the entire stack

2. kraiken-lib Version Tracking

File: kraiken-lib/src/version.ts

export const KRAIKEN_LIB_VERSION = 1;

export const COMPATIBLE_CONTRACT_VERSIONS = [1];

export function isCompatibleVersion(contractVersion: number): boolean {
  return COMPATIBLE_CONTRACT_VERSIONS.includes(contractVersion);
}

Key features:

  • Manually maintained list (not parsed from contract)
  • Allows backward compatibility (lib can support multiple contract versions)
  • Exported by kraiken-lib/src/index.ts for all consumers

3. Ponder Validation (FAIL HARD)

File: services/ponder/src/helpers/version.ts

export async function validateContractVersion(context: Context): Promise<void> {
  const contractVersion = await context.client.readContract({
    address: context.contracts.Kraiken.address,
    abi: context.contracts.Kraiken.abi,
    functionName: 'VERSION',
  });

  if (!isCompatibleVersion(Number(contractVersion))) {
    console.error(getVersionMismatchError(contractVersion, 'ponder'));
    process.exit(1);  // FAIL HARD
  }
}

Called in: services/ponder/src/kraiken.ts (first Transfer event)

Behavior:

  • Reads VERSION from deployed Kraiken contract
  • Compares against COMPATIBLE_CONTRACT_VERSIONS
  • Exits with error if incompatible (prevents indexing wrong data)
  • Logs success if compatible

4. Frontend Validation (WARN)

File: web-app/src/composables/useVersionCheck.ts

export function useVersionCheck() {
  async function checkVersions(graphqlUrl: string) {
    // Currently placeholder - validates lib loaded correctly
    // Future: Query Ponder for stored contract version
    versionStatus.value = {
      isValid: true,
      libVersion: KRAIKEN_LIB_VERSION,
    };
  }
  // ...
}

Behavior:

  • Validates KRAIKEN_LIB_VERSION is loaded
  • Future enhancement: Query Ponder GraphQL for contract version
  • Shows warning banner if mismatch (doesn't break app)

5. CI/CD Validation

File: .github/workflows/validate-version.yml

- name: Extract versions and validate
  run: |
    CONTRACT_VERSION=$(grep -oP 'VERSION\s*=\s*\K\d+' onchain/src/Kraiken.sol)
    LIB_VERSION=$(grep -oP 'KRAIKEN_LIB_VERSION\s*=\s*\K\d+' kraiken-lib/src/version.ts)
    COMPATIBLE=$(grep -oP 'COMPATIBLE_CONTRACT_VERSIONS\s*=\s*\[\K[^\]]+' kraiken-lib/src/version.ts)
    
    if echo ",$COMPATIBLE," | grep -q ",$CONTRACT_VERSION,"; then
      echo "✓ Version sync validated"
    else
      exit 1
    fi

Triggered on:

  • PRs touching Kraiken.sol or version.ts
  • Pushes to master/main

Prevents:

  • Merging incompatible versions
  • Deploying with stale kraiken-lib

Workflows

Version Bump (Breaking Change)

When modifying TAX_RATES or other critical data structures:

1. Edit onchain/src/Kraiken.sol
   uint256 public constant VERSION = 2;  // Increment

2. Edit kraiken-lib/src/version.ts
   export const KRAIKEN_LIB_VERSION = 2;
   export const COMPATIBLE_CONTRACT_VERSIONS = [2];  // Or [1, 2] for backward compat

3. Rebuild kraiken-lib
   ./scripts/build-kraiken-lib.sh

4. Clear Ponder state
   rm -rf services/ponder/.ponder/

5. Redeploy contracts (if needed)
   cd onchain && forge script ...

6. Restart stack
   ./scripts/dev.sh restart --full

Adding Backward Compatibility

If lib can work with old AND new contract versions:

// kraiken-lib/src/version.ts
export const KRAIKEN_LIB_VERSION = 2;
export const COMPATIBLE_CONTRACT_VERSIONS = [1, 2];  // Supports both

Ponder will accept either v1 or v2 contracts.

Troubleshooting

Ponder fails with "VERSION MISMATCH"

╔════════════════════════════════════════════════════════════╗
║  ❌ CRITICAL: VERSION MISMATCH (ponder)                     
╠════════════════════════════════════════════════════════════╣
║  Contract VERSION:      2
║  Library VERSION:       1
║  Compatible versions:   1

Fix:

  1. Check if contract was upgraded
  2. Update COMPATIBLE_CONTRACT_VERSIONS in kraiken-lib/src/version.ts
  3. Run ./scripts/build-kraiken-lib.sh
  4. Run rm -rf services/ponder/.ponder/
  5. Restart Ponder

CI/CD check fails

❌ Contract VERSION (2) not in COMPATIBLE_CONTRACT_VERSIONS

Fix:

  • Update kraiken-lib/src/version.ts to include new version
  • Or revert contract changes if unintentional

Benefits

Prevents data corruption - Ponder won't index with wrong schema
Early detection - Fails at startup, not after hours of indexing
Clear errors - Tells you exactly what to do
Backward compat - Can support multiple versions if needed
CI enforcement - Prevents merging incompatible changes
Future-proof - Easy to extend to other contracts (Stake, LiquidityManager)

Future Enhancements

  1. Store version in Ponder stats table

    • Add contractVersion column to stats
    • Frontend can query via GraphQL
    • Enables full 3-way validation (contract → Ponder → frontend)
  2. Multi-contract versioning

    • Add VERSION to Stake.sol, LiquidityManager.sol
    • Track all contract versions in kraiken-lib
    • Validate full deployment is compatible
  3. Version migration helpers

    • Scripts to handle data migrations
    • Backward-compatible event handling
    • Gradual rollout support

Maintenance

When to increment VERSION

Increment when changes affect indexed data or frontend interpretation:

  • Modifying TAX_RATES array
  • Adding/removing event parameters
  • Changing event names
  • Modifying struct definitions that frontends consume
  • Breaking changes to staking logic

Don't increment for:

  • Gas optimizations (no data changes)
  • Internal logic changes (if events unchanged)
  • Comment updates
  • Pure view function changes

Documentation

Update VERSION comment in contract with each change:

/**
 * Version History:
 * - v1: Initial deployment (30-tier TAX_RATES)
 * - v2: Added new tax tier at 150% (31 tiers total)
 * - v3: Modified PositionCreated event (added taxRateIndex field)
 */
uint256 public constant VERSION = 3;

This creates an audit trail for version changes.