Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
[package]
name = "ruvector-economy-wasm"
version = "0.1.0"
edition = "2021"
authors = ["RuVector Team"]
license = "MIT"
description = "CRDT-based autonomous credit economy for distributed compute networks - WASM optimized"
repository = "https://github.com/ruvnet/ruvector"
keywords = ["wasm", "crdt", "distributed-economy", "p2p-credits", "reputation"]
categories = ["wasm", "cryptography", "data-structures"]
readme = "README.md"
[lib]
crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[features]
default = ["console_error_panic_hook"]
# Enable full QDAG ledger support
qdag = []
# Enable reputation scoring
reputation = []
# Enable all features
full = ["qdag", "reputation"]
[dependencies]
# WASM bindings
wasm-bindgen = "0.2"
js-sys = "0.3"
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Fast hashing for CRDT maps
rustc-hash = "2.0"
# Cryptographic hashing for Merkle roots
sha2 = { version = "0.10", default-features = false }
# Error handling for WASM
console_error_panic_hook = { version = "0.1", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3"
[profile.release]
lto = true
opt-level = "s"
codegen-units = 1
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-Os", "--enable-bulk-memory", "--enable-nontrapping-float-to-int"]

View File

@@ -0,0 +1,406 @@
# ruvector-economy-wasm
CRDT-based autonomous credit economy for distributed compute networks. Designed for WASM execution with P2P consistency guarantees.
## Installation
```bash
npm install ruvector-economy-wasm
```
## Quick Start
```javascript
import init, {
CreditLedger,
ReputationScore,
StakeManager,
contribution_multiplier,
calculate_reward,
get_tier_name
} from 'ruvector-economy-wasm';
// Initialize the WASM module
await init();
// Create a credit ledger for a node
const ledger = new CreditLedger("node-123");
// Earn credits
ledger.credit(100n, "task:abc");
console.log(`Balance: ${ledger.balance()}`);
// Check early adopter multiplier
const mult = contribution_multiplier(50000.0);
console.log(`Multiplier: ${mult}x`); // ~9.5x for early network
// Track reputation
const rep = new ReputationScore(0.95, 0.98, 1000n);
console.log(`Composite score: ${rep.compositeScore()}`);
```
## Architecture
```
+------------------------+
| CreditLedger | <-- CRDT-based P2P-safe ledger
| +------------------+ |
| | G-Counter: Earned| | <-- Monotonically increasing
| | PN-Counter: Spent| | <-- Supports dispute resolution
| | Stake: Locked | | <-- Participation requirement
| | State Root | | <-- Merkle root for verification
| +------------------+ |
+------------------------+
|
v
+------------------------+
| ContributionCurve | <-- Exponential decay: 10x -> 1x
+------------------------+
|
v
+------------------------+
| ReputationScore | <-- accuracy * uptime * stake_weight
+------------------------+
|
v
+------------------------+
| StakeManager | <-- Delegation, slashing, lock periods
+------------------------+
```
## API Reference
### CreditLedger
The core CRDT ledger for tracking credits earned, spent, and staked.
```typescript
class CreditLedger {
// Constructor
constructor(node_id: string);
// Balance operations
balance(): bigint; // Current available balance
totalEarned(): bigint; // Total credits ever earned
totalSpent(): bigint; // Total credits spent (net of refunds)
stakedAmount(): bigint; // Currently staked amount
// Credit operations
credit(amount: bigint, reason: string): string; // Returns event_id
creditWithMultiplier(base_amount: bigint, reason: string): string;
deduct(amount: bigint): string; // Returns event_id
refund(event_id: string, amount: bigint): void;
// Staking
stake(amount: bigint): void;
unstake(amount: bigint): void;
slash(amount: bigint): bigint; // Returns amount actually slashed
// Early adopter multiplier
currentMultiplier(): number;
networkCompute(): number;
updateNetworkCompute(hours: number): void;
// State verification
stateRoot(): Uint8Array;
stateRootHex(): string;
verifyStateRoot(expected_root: Uint8Array): boolean;
// P2P sync (CRDT merge)
merge(other_earned: Uint8Array, other_spent: Uint8Array): number;
exportEarned(): Uint8Array;
exportSpent(): Uint8Array;
// Utilities
nodeId(): string;
eventCount(): number;
free(): void; // Release WASM memory
}
```
#### Example: CRDT Merge for P2P Sync
```javascript
// Node A creates ledger
const ledgerA = new CreditLedger("node-A");
ledgerA.credit(100n, "task:1");
ledgerA.credit(50n, "task:2");
// Node B creates ledger
const ledgerB = new CreditLedger("node-B");
ledgerB.credit(75n, "task:3");
// Export state for sync
const earnedA = ledgerA.exportEarned();
const spentA = ledgerA.exportSpent();
// Merge on node B (CRDT: associative, commutative, idempotent)
const mergedCount = ledgerB.merge(earnedA, spentA);
console.log(`Merged ${mergedCount} entries`);
```
### ContributionCurve (via standalone functions)
Early adopter reward multiplier with exponential decay.
```typescript
// Get multiplier for network compute level
function contribution_multiplier(network_compute_hours: number): number;
// Calculate reward with multiplier applied
function calculate_reward(base_reward: bigint, network_compute_hours: number): bigint;
// Get tier name for UI display
function get_tier_name(network_compute_hours: number): string;
// Get all tier thresholds as JSON
function get_tiers_json(): string;
```
#### Multiplier Curve
```
Multiplier
10x |*
| *
8x | *
| *
6x | *
| *
4x | *
| **
2x | ***
| *****
1x | ****************************
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---> Network Compute (M hours)
0 1 2 3 4 5 6 7 8 9 10
```
#### Tier Reference
| Tier | Network Compute | Multiplier |
|------|-----------------|------------|
| Genesis | 0 - 100K hours | ~10x |
| Pioneer | 100K - 500K hours | ~9x - 6x |
| Early Adopter | 500K - 1M hours | ~6x - 4x |
| Established | 1M - 5M hours | ~4x - 1.5x |
| Baseline | 5M+ hours | ~1x |
#### Example: Early Adopter Rewards
```javascript
// Genesis contributor (first on network)
const genesisMultiplier = contribution_multiplier(0);
console.log(genesisMultiplier); // 10.0
// Task completion reward
const baseReward = 100n;
const actualReward = calculate_reward(baseReward, 50000.0);
console.log(actualReward); // ~950 (9.5x for early network)
// Display tier to user
const tier = get_tier_name(500000.0);
console.log(tier); // "Early Adopter"
```
### ReputationScore
Multi-factor reputation scoring for node quality assessment.
```typescript
class ReputationScore {
// Constructors
constructor(accuracy: number, uptime: number, stake: bigint);
static newWithTracking(
tasks_completed: bigint,
tasks_failed: bigint,
uptime_seconds: bigint,
total_seconds: bigint,
stake: bigint
): ReputationScore;
// Core scores
readonly accuracy: number; // 0.0 - 1.0
readonly uptime: number; // 0.0 - 1.0
readonly stake: bigint;
// Calculated scores
compositeScore(): number; // accuracy^2 * uptime * stake_weight
stakeWeight(): number; // log10(stake + 1) / 6, capped at 1.0
tierName(): string; // "Elite", "Reliable", "Standard", "Novice"
// Task tracking
recordSuccess(): void;
recordFailure(): void;
tasksCompleted(): bigint;
tasksFailed(): bigint;
totalTasks(): bigint;
// Uptime tracking
updateUptime(online_seconds: bigint, total_seconds: bigint): void;
// Stake management
updateStake(new_stake: bigint): void;
// Comparisons
isBetterThan(other: ReputationScore): boolean;
meetsMinimum(min_accuracy: number, min_uptime: number, min_stake: bigint): boolean;
// Serialization
toJson(): string;
static fromJson(json: string): ReputationScore;
free(): void;
}
```
#### Composite Score Formula
```
composite_score = accuracy^2 * uptime * stake_weight
```
Where:
- `accuracy` = tasks_completed / total_tasks
- `uptime` = online_seconds / total_seconds
- `stake_weight` = min(1.0, log10(stake + 1) / 6)
#### Example: Reputation Tracking
```javascript
// Create with detailed tracking
const rep = ReputationScore.newWithTracking(
95n, // tasks completed
5n, // tasks failed
86400n, // uptime seconds (24 hours)
90000n, // total seconds (25 hours)
10000n // stake amount
);
console.log(`Accuracy: ${rep.accuracy}`); // 0.95
console.log(`Uptime: ${rep.uptime}`); // 0.96
console.log(`Stake Weight: ${rep.stakeWeight()}`); // ~0.67
console.log(`Composite: ${rep.compositeScore()}`); // ~0.58
console.log(`Tier: ${rep.tierName()}`); // "Reliable"
// Track ongoing performance
rep.recordSuccess();
rep.recordSuccess();
rep.recordFailure();
console.log(`New accuracy: ${rep.tasksCompleted()} / ${rep.totalTasks()}`);
// Check if meets minimum requirements
const eligible = rep.meetsMinimum(0.9, 0.95, 1000n);
console.log(`Eligible for premium tasks: ${eligible}`);
```
### StakeManager
Network-wide stake management with delegation and slashing.
```typescript
class StakeManager {
// Constructors
constructor();
static newWithParams(min_stake: bigint, lock_period_ms: bigint): StakeManager;
// Staking
stake(node_id: string, amount: bigint): void;
unstake(node_id: string, amount: bigint): bigint; // Returns actual unstaked
getStake(node_id: string): bigint;
// Delegation
delegate(from_node: string, to_node: string, amount: bigint): void;
undelegate(from_node: string, to_node: string, amount: bigint): void;
getEffectiveStake(node_id: string): bigint; // own + delegated
getDelegatorCount(node_id: string): number;
// Slashing
slash(node_id: string, reason: SlashReason, evidence: string): bigint;
getSlashCount(node_id: string): number;
getNodeTotalSlashed(node_id: string): bigint;
// Lock management
isLocked(node_id: string): boolean;
getLockTimestamp(node_id: string): bigint;
// Network stats
totalStaked(): bigint;
totalSlashed(): bigint;
stakerCount(): number;
minStake(): bigint;
meetsMinimum(node_id: string): boolean;
// Export
exportJson(): string;
free(): void;
}
enum SlashReason {
InvalidResult = 0,
DoubleSpend = 1,
SybilAttack = 2,
Downtime = 3,
Spam = 4,
Malicious = 5
}
```
#### Example: Stake Delegation
```javascript
const manager = StakeManager.newWithParams(100n, 86400000n); // 100 min, 24h lock
// Nodes stake
manager.stake("validator-1", 10000n);
manager.stake("delegator-1", 500n);
// Delegator delegates to validator
manager.delegate("delegator-1", "validator-1", 500n);
// Check effective stake
const effective = manager.getEffectiveStake("validator-1");
console.log(`Validator effective stake: ${effective}`); // 10500
// Slash for bad behavior
const slashed = manager.slash("validator-1", SlashReason.InvalidResult, "proof:xyz");
console.log(`Slashed: ${slashed}`);
```
## Standalone Functions
```typescript
// Contribution curve
function contribution_multiplier(network_compute_hours: number): number;
function calculate_reward(base_reward: bigint, network_compute_hours: number): bigint;
function get_tier_name(network_compute_hours: number): string;
function get_tiers_json(): string;
// Reputation helpers
function composite_reputation(accuracy: number, uptime: number, stake: bigint): number;
function stake_weight(stake: bigint): number;
// Module info
function version(): string;
function init_panic_hook(): void;
```
## WASM Bundle Information
| File | Size | Description |
|------|------|-------------|
| `ruvector_economy_wasm_bg.wasm` | 178 KB | WebAssembly binary |
| `ruvector_economy_wasm.js` | 47 KB | JavaScript bindings |
| `ruvector_economy_wasm.d.ts` | 15 KB | TypeScript definitions |
## Browser Compatibility
- Chrome 89+ (WebAssembly bulk memory, nontrapping-fptoint)
- Firefox 89+
- Safari 15+
- Edge 89+
## License
MIT

View File

@@ -0,0 +1,303 @@
# @ruvector/economy-wasm - CRDT Credit Economy for Distributed Compute
[![npm version](https://img.shields.io/npm/v/ruvector-economy-wasm.svg)](https://www.npmjs.com/package/ruvector-economy-wasm)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ruvnet/ruvector)
[![Bundle Size](https://img.shields.io/badge/bundle%20size-177KB%20gzip-green.svg)](https://www.npmjs.com/package/ruvector-economy-wasm)
[![WebAssembly](https://img.shields.io/badge/WebAssembly-654FF0?logo=webassembly&logoColor=white)](https://webassembly.org/)
A **CRDT-based autonomous credit economy** for distributed compute networks. Provides conflict-free P2P credit tracking, stake/slash mechanics, and reputation scoring - a blockchain alternative for edge computing and AI agent coordination.
## Key Features
- **CRDT Ledger**: G-Counter and PN-Counter for P2P-safe credit tracking with guaranteed eventual consistency
- **10x Early Adopter Curve**: Contribution multiplier decaying from 10x to 1x baseline as network grows
- **Stake/Slash Mechanics**: Participation requirements with slashing for sybil attacks, double-spending, and bad behavior
- **Reputation Scoring**: Multi-factor composite score based on accuracy, uptime, and stake weight
- **Merkle State Root**: Fast ledger verification with cryptographic proofs
- **WASM-Optimized**: Runs in browsers, Node.js, and edge runtimes
## Installation
```bash
npm install ruvector-economy-wasm
# or
yarn add ruvector-economy-wasm
# or
pnpm add ruvector-economy-wasm
```
## Quick Start
### TypeScript/JavaScript
```typescript
import init, {
CreditLedger,
ReputationScore,
StakeManager,
contribution_multiplier,
SlashReason
} from 'ruvector-economy-wasm';
// Initialize WASM module
await init();
// Create a credit ledger for a node
const ledger = new CreditLedger("node-123");
// Earn credits for completed tasks
ledger.credit(100n, "task:compute-job-456");
console.log(`Balance: ${ledger.balance()}`);
// Check early adopter multiplier
const mult = contribution_multiplier(50000.0); // 50K network compute hours
console.log(`Multiplier: ${mult.toFixed(2)}x`); // ~8.5x for early adopters
// Credit with multiplier applied
ledger.creditWithMultiplier(50n, "task:bonus-789");
// Track reputation
const rep = new ReputationScore(0.95, 0.98, 1000n);
console.log(`Composite score: ${rep.compositeScore()}`);
console.log(`Tier: ${rep.tierName()}`);
```
## Understanding CRDTs
**Conflict-free Replicated Data Types (CRDTs)** enable distributed systems to:
- Merge updates in any order with identical results
- Operate offline and sync later without conflicts
- Scale horizontally without coordination bottlenecks
This package uses:
- **G-Counter**: Grow-only counter for earned credits (monotonically increasing)
- **PN-Counter**: Positive-negative counter for spending (allows refunds/disputes)
```typescript
// P2P merge example - works regardless of message order
const nodeA = new CreditLedger("node-A");
const nodeB = new CreditLedger("node-B");
// Both nodes earn credits independently
nodeA.credit(100n, "job-1");
nodeB.credit(50n, "job-2");
// Export for P2P sync
const earnedA = nodeA.exportEarned();
const spentA = nodeA.exportSpent();
// Merge on node B - associative, commutative, idempotent
const merged = nodeB.merge(earnedA, spentA);
console.log(`Merged ${merged} updates`);
```
## Contribution Curve
Early network contributors receive higher rewards that decay as the network matures:
```
Multiplier = 1 + 9 * exp(-compute_hours / 100,000)
Network Hours | Multiplier
---------------|------------
0 | 10.0x (Genesis)
10,000 | ~9.0x
50,000 | ~6.0x
100,000 | ~4.3x
200,000 | ~2.2x
500,000 | ~1.0x (Baseline)
```
```typescript
import { contribution_multiplier, get_tier_name, get_tiers_json } from 'ruvector-economy-wasm';
// Check current multiplier
const hours = 25000;
const mult = contribution_multiplier(hours);
console.log(`At ${hours} hours: ${mult.toFixed(2)}x multiplier`);
// Get tier name
const tier = get_tier_name(hours); // "Pioneer"
// Get all tier definitions
const tiers = JSON.parse(get_tiers_json());
```
## Stake/Slash Mechanics
```typescript
import { StakeManager, SlashReason } from 'ruvector-economy-wasm';
const stakeManager = new StakeManager();
// Stake credits for network participation
stakeManager.stake("node-123", 1000n);
console.log(`Staked: ${stakeManager.getStake("node-123")}`);
// Check if node meets minimum stake
if (stakeManager.meetsMinimum("node-123")) {
console.log("Node can participate");
}
// Delegate stake to another node
stakeManager.delegate("node-123", "validator-1", 500n);
console.log(`Effective stake: ${stakeManager.getEffectiveStake("validator-1")}`);
// Slash for bad behavior
const slashedAmount = stakeManager.slash(
"bad-actor",
SlashReason.DoubleSpend,
"Evidence: duplicate transaction IDs"
);
console.log(`Slashed ${slashedAmount} credits`);
```
### Slash Reasons
| Reason | Severity | Description |
|--------|----------|-------------|
| `InvalidResult` | Medium | Submitted incorrect computation results |
| `DoubleSpend` | High | Attempted to spend same credits twice |
| `SybilAttack` | Critical | Multiple fake identities detected |
| `Downtime` | Low | Excessive offline periods |
| `Spam` | Medium | Flooding the network |
| `Malicious` | Critical | Intentional harmful behavior |
## Reputation System
```typescript
import { ReputationScore, composite_reputation } from 'ruvector-economy-wasm';
// Create reputation with tracking
const rep = ReputationScore.newWithTracking(
950n, // tasks completed
50n, // tasks failed
BigInt(30 * 24 * 3600), // uptime seconds
BigInt(31 * 24 * 3600), // total seconds
5000n // stake amount
);
// Record task outcomes
rep.recordSuccess();
rep.recordFailure();
// Calculate composite score
// Formula: accuracy^2 * uptime * stake_weight
const score = rep.compositeScore();
console.log(`Composite: ${(score * 100).toFixed(1)}%`);
// Get tier
console.log(`Tier: ${rep.tierName()}`); // "Elite", "Trusted", "Standard", etc.
// Check participation eligibility
if (rep.meetsMinimum(0.9, 0.95, 100n)) {
console.log("Eligible for premium tasks");
}
// Compare reputations
const rep2 = new ReputationScore(0.92, 0.96, 3000n);
console.log(`Better reputation: ${rep.isBetterThan(rep2) ? 'rep1' : 'rep2'}`);
```
### Reputation Tiers
| Tier | Score Range | Benefits |
|------|-------------|----------|
| Elite | >= 0.95 | Priority task assignment, lowest fees |
| Trusted | >= 0.85 | High-value tasks, reduced collateral |
| Standard | >= 0.70 | Normal participation |
| Probation | >= 0.50 | Limited task types |
| Restricted | < 0.50 | Basic tasks only, increased monitoring |
## Merkle State Verification
```typescript
const ledger = new CreditLedger("node-123");
ledger.credit(100n, "job-1");
ledger.credit(200n, "job-2");
// Get state root for verification
const stateRoot = ledger.stateRoot();
const stateRootHex = ledger.stateRootHex();
console.log(`State root: ${stateRootHex}`);
// Verify state integrity
const isValid = ledger.verifyStateRoot(expectedRoot);
```
## API Reference
### CreditLedger
| Method | Description |
|--------|-------------|
| `new(node_id)` | Create ledger for node |
| `credit(amount, reason)` | Earn credits |
| `creditWithMultiplier(base_amount, reason)` | Earn with network multiplier |
| `deduct(amount)` | Spend credits |
| `refund(event_id, amount)` | Refund a deduction |
| `balance()` | Get available balance |
| `stake(amount)` | Lock credits for participation |
| `slash(amount)` | Penalty for bad behavior |
| `merge(other_earned, other_spent)` | CRDT merge operation |
| `exportEarned()` / `exportSpent()` | Export for P2P sync |
| `stateRoot()` / `stateRootHex()` | Merkle verification |
### ReputationScore
| Method | Description |
|--------|-------------|
| `new(accuracy, uptime, stake)` | Create with scores |
| `newWithTracking(...)` | Create with detailed tracking |
| `compositeScore()` | Calculate composite (0.0-1.0) |
| `tierName()` | Get reputation tier |
| `recordSuccess()` / `recordFailure()` | Track task outcomes |
| `stakeWeight()` | Logarithmic stake weight |
| `meetsMinimum(accuracy, uptime, stake)` | Check eligibility |
### StakeManager
| Method | Description |
|--------|-------------|
| `new()` | Create manager |
| `stake(node_id, amount)` | Stake credits |
| `unstake(node_id, amount)` | Unstake (if unlocked) |
| `delegate(from, to, amount)` | Delegate to another node |
| `slash(node_id, reason, evidence)` | Slash for violation |
| `getEffectiveStake(node_id)` | Own + delegated stake |
| `meetsMinimum(node_id)` | Check stake requirement |
## Use Cases
- **Distributed AI Training**: Reward compute contributors fairly
- **Edge Computing Networks**: Track and reward edge node participation
- **Federated Learning**: Incentivize model training contributions
- **P2P Storage**: Credit-based storage allocation
- **Agent Coordination**: Economic layer for multi-agent systems
- **Decentralized Inference**: Pay-per-inference without blockchain overhead
## Bundle Size
- **WASM binary**: ~177KB (uncompressed)
- **Gzip compressed**: ~65KB
- **JavaScript glue**: ~8KB
## Related Packages
- [ruvector-learning-wasm](https://www.npmjs.com/package/ruvector-learning-wasm) - MicroLoRA adaptation
- [ruvector-exotic-wasm](https://www.npmjs.com/package/ruvector-exotic-wasm) - NAO governance, morphogenetic networks
- [ruvector-nervous-system-wasm](https://www.npmjs.com/package/ruvector-nervous-system-wasm) - Bio-inspired neural components
## License
MIT
## Links
- [GitHub Repository](https://github.com/ruvnet/ruvector)
- [Full Documentation](https://ruv.io)
- [Bug Reports](https://github.com/ruvnet/ruvector/issues)
---
**Keywords**: CRDT, distributed systems, credits, P2P, peer-to-peer, blockchain alternative, reputation, stake, slash, economy, WebAssembly, WASM, edge computing, decentralized, conflict-free, eventual consistency, G-Counter, PN-Counter

View File

@@ -0,0 +1,43 @@
{
"name": "@ruvector/economy-wasm",
"type": "module",
"collaborators": [
"RuVector Team"
],
"author": "RuVector Team <ruvnet@users.noreply.github.com>",
"description": "CRDT-based autonomous credit economy for distributed compute networks - WASM optimized",
"version": "0.1.29",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ruvnet/ruvector"
},
"bugs": {
"url": "https://github.com/ruvnet/ruvector/issues"
},
"files": [
"ruvector_economy_wasm_bg.wasm",
"ruvector_economy_wasm.js",
"ruvector_economy_wasm.d.ts",
"ruvector_economy_wasm_bg.wasm.d.ts",
"README.md"
],
"main": "ruvector_economy_wasm.js",
"homepage": "https://ruv.io",
"types": "ruvector_economy_wasm.d.ts",
"sideEffects": [
"./snippets/*"
],
"keywords": [
"wasm",
"crdt",
"distributed-economy",
"p2p-credits",
"reputation",
"ruvector",
"webassembly",
"distributed-systems",
"autonomous-economy",
"consensus"
]
}

View File

@@ -0,0 +1,468 @@
/* tslint:disable */
/* eslint-disable */
export class CreditLedger {
free(): void;
[Symbol.dispose](): void;
/**
* Get the state root (Merkle root of ledger state)
*/
stateRoot(): Uint8Array;
/**
* Get event count
*/
eventCount(): number;
/**
* Get total credits spent
*/
totalSpent(): bigint;
/**
* Export spent counter for P2P sync
*/
exportSpent(): Uint8Array;
/**
* Get total credits ever earned (before spending)
*/
totalEarned(): bigint;
/**
* Export earned counter for P2P sync
*/
exportEarned(): Uint8Array;
/**
* Get staked amount
*/
stakedAmount(): bigint;
/**
* Get state root as hex string
*/
stateRootHex(): string;
/**
* Get network compute hours
*/
networkCompute(): number;
/**
* Verify state root matches current state
*/
verifyStateRoot(expected_root: Uint8Array): boolean;
/**
* Get current contribution multiplier
*/
currentMultiplier(): number;
/**
* Credit with multiplier applied (for task rewards)
*/
creditWithMultiplier(base_amount: bigint, reason: string): string;
/**
* Update network compute hours (from P2P sync)
*/
updateNetworkCompute(hours: number): void;
/**
* Create a new credit ledger for a node
*/
constructor(node_id: string);
/**
* Merge with another ledger (CRDT merge operation)
*
* This is the core CRDT operation - associative, commutative, and idempotent.
* Safe to apply in any order with any number of concurrent updates.
*/
merge(other_earned: Uint8Array, other_spent: Uint8Array): number;
/**
* Slash staked credits (penalty for bad behavior)
*
* Returns the actual amount slashed (may be less if stake is insufficient)
*/
slash(amount: bigint): bigint;
/**
* Stake credits for participation
*/
stake(amount: bigint): void;
/**
* Credit the ledger (earn credits)
*
* This updates the G-Counter which is monotonically increasing.
* Safe for concurrent P2P updates.
*/
credit(amount: bigint, _reason: string): string;
/**
* Deduct from the ledger (spend credits)
*
* This updates the PN-Counter positive side.
* Spending can be disputed/refunded by updating the negative side.
*/
deduct(amount: bigint): string;
/**
* Refund a previous deduction (dispute resolution)
*
* This updates the PN-Counter negative side for the given event.
*/
refund(event_id: string, amount: bigint): void;
/**
* Get current available balance (earned - spent - staked)
*/
balance(): bigint;
/**
* Get the node ID
*/
nodeId(): string;
/**
* Unstake credits
*/
unstake(amount: bigint): void;
}
export class ReputationScore {
free(): void;
[Symbol.dispose](): void;
/**
* Get total tasks
*/
totalTasks(): bigint;
/**
* Calculate stake weight using logarithmic scaling
*
* Uses log10(stake + 1) / 6 capped at 1.0
* This means:
* - 0 stake = 0.0 weight
* - 100 stake = ~0.33 weight
* - 10,000 stake = ~0.67 weight
* - 1,000,000 stake = 1.0 weight (capped)
*/
stakeWeight(): number;
/**
* Get tasks failed
*/
tasksFailed(): bigint;
/**
* Update stake amount
*/
updateStake(new_stake: bigint): void;
/**
* Check if node meets minimum reputation for participation
*/
meetsMinimum(min_accuracy: number, min_uptime: number, min_stake: bigint): boolean;
/**
* Update uptime tracking
*/
updateUptime(online_seconds: bigint, total_seconds: bigint): void;
/**
* Check if this reputation is better than another
*/
isBetterThan(other: ReputationScore): boolean;
/**
* Record a failed/disputed task
*/
recordFailure(): void;
/**
* Record a successful task completion
*/
recordSuccess(): void;
/**
* Calculate composite reputation score
*
* Formula: accuracy^2 * uptime * stake_weight
*
* Returns a value between 0.0 and 1.0
*/
compositeScore(): number;
/**
* Get tasks completed
*/
tasksCompleted(): bigint;
/**
* Create with detailed tracking
*/
static newWithTracking(tasks_completed: bigint, tasks_failed: bigint, uptime_seconds: bigint, total_seconds: bigint, stake: bigint): ReputationScore;
/**
* Create a new reputation score
*/
constructor(accuracy: number, uptime: number, stake: bigint);
/**
* Serialize to JSON
*/
toJson(): string;
/**
* Deserialize from JSON
*/
static fromJson(json: string): ReputationScore;
/**
* Get reputation tier based on composite score
*/
tierName(): string;
/**
* Get stake amount
*/
readonly stake: bigint;
/**
* Get uptime score (0.0 - 1.0)
*/
readonly uptime: number;
/**
* Get accuracy score (0.0 - 1.0)
*/
readonly accuracy: number;
}
/**
* Reasons for slashing stake
*/
export enum SlashReason {
/**
* Invalid task result
*/
InvalidResult = 0,
/**
* Double-spending attempt
*/
DoubleSpend = 1,
/**
* Sybil attack detected
*/
SybilAttack = 2,
/**
* Excessive downtime
*/
Downtime = 3,
/**
* Spam/flooding
*/
Spam = 4,
/**
* Malicious behavior
*/
Malicious = 5,
}
export class StakeManager {
free(): void;
[Symbol.dispose](): void;
/**
* Undelegate stake
*/
undelegate(from_node: string, to_node: string, amount: bigint): void;
/**
* Export stake data as JSON
*/
exportJson(): string;
/**
* Get number of stakers
*/
stakerCount(): number;
/**
* Get total network staked
*/
totalStaked(): bigint;
/**
* Check if node meets minimum stake
*/
meetsMinimum(node_id: string): boolean;
/**
* Get total slashed
*/
totalSlashed(): bigint;
/**
* Get slash count for a node
*/
getSlashCount(node_id: string): number;
/**
* Create with custom parameters
*/
static newWithParams(min_stake: bigint, lock_period_ms: bigint): StakeManager;
/**
* Get lock timestamp for a node
*/
getLockTimestamp(node_id: string): bigint;
/**
* Get delegator count
*/
getDelegatorCount(node_id: string): number;
/**
* Get effective stake (own + delegated)
*/
getEffectiveStake(node_id: string): bigint;
/**
* Get total amount slashed from a node
*/
getNodeTotalSlashed(node_id: string): bigint;
/**
* Create a new stake manager
*/
constructor();
/**
* Slash stake for bad behavior
*/
slash(node_id: string, reason: SlashReason, evidence: string): bigint;
/**
* Stake credits for a node
*/
stake(node_id: string, amount: bigint): void;
/**
* Unstake credits (if lock period has passed)
*/
unstake(node_id: string, amount: bigint): bigint;
/**
* Delegate stake to another node
*/
delegate(from_node: string, to_node: string, amount: bigint): void;
/**
* Get stake for a node
*/
getStake(node_id: string): bigint;
/**
* Check if stake is locked
*/
isLocked(node_id: string): boolean;
/**
* Get minimum stake requirement
*/
minStake(): bigint;
}
/**
* Calculate reward with multiplier (WASM export)
*/
export function calculate_reward(base_reward: bigint, network_compute_hours: number): bigint;
/**
* Calculate composite reputation score (WASM export)
*/
export function composite_reputation(accuracy: number, uptime: number, stake: bigint): number;
/**
* Calculate contribution multiplier (WASM export)
*
* Returns the reward multiplier based on total network compute hours.
* Early adopters get up to 10x rewards, decaying to 1x as network grows.
*/
export function contribution_multiplier(network_compute_hours: number): number;
/**
* Get tier name based on compute level (WASM export)
*/
export function get_tier_name(network_compute_hours: number): string;
/**
* Get tier information as JSON (WASM export)
*/
export function get_tiers_json(): string;
/**
* Initialize panic hook for better error messages in console
*/
export function init_panic_hook(): void;
/**
* Calculate stake weight (WASM export)
*/
export function stake_weight(stake: bigint): number;
/**
* Get the current version of the economy module
*/
export function version(): string;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly __wbg_creditledger_free: (a: number, b: number) => void;
readonly __wbg_reputationscore_free: (a: number, b: number) => void;
readonly __wbg_stakemanager_free: (a: number, b: number) => void;
readonly calculate_reward: (a: bigint, b: number) => bigint;
readonly composite_reputation: (a: number, b: number, c: bigint) => number;
readonly contribution_multiplier: (a: number) => number;
readonly creditledger_balance: (a: number) => bigint;
readonly creditledger_credit: (a: number, b: number, c: bigint, d: number, e: number) => void;
readonly creditledger_creditWithMultiplier: (a: number, b: number, c: bigint, d: number, e: number) => void;
readonly creditledger_currentMultiplier: (a: number) => number;
readonly creditledger_deduct: (a: number, b: number, c: bigint) => void;
readonly creditledger_eventCount: (a: number) => number;
readonly creditledger_exportEarned: (a: number, b: number) => void;
readonly creditledger_exportSpent: (a: number, b: number) => void;
readonly creditledger_merge: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
readonly creditledger_networkCompute: (a: number) => number;
readonly creditledger_new: (a: number, b: number, c: number) => void;
readonly creditledger_nodeId: (a: number, b: number) => void;
readonly creditledger_refund: (a: number, b: number, c: number, d: number, e: bigint) => void;
readonly creditledger_slash: (a: number, b: number, c: bigint) => void;
readonly creditledger_stake: (a: number, b: number, c: bigint) => void;
readonly creditledger_stakedAmount: (a: number) => bigint;
readonly creditledger_stateRoot: (a: number, b: number) => void;
readonly creditledger_stateRootHex: (a: number, b: number) => void;
readonly creditledger_totalEarned: (a: number) => bigint;
readonly creditledger_totalSpent: (a: number) => bigint;
readonly creditledger_unstake: (a: number, b: number, c: bigint) => void;
readonly creditledger_updateNetworkCompute: (a: number, b: number) => void;
readonly creditledger_verifyStateRoot: (a: number, b: number, c: number) => number;
readonly get_tier_name: (a: number, b: number) => void;
readonly get_tiers_json: (a: number) => void;
readonly reputationscore_accuracy: (a: number) => number;
readonly reputationscore_compositeScore: (a: number) => number;
readonly reputationscore_fromJson: (a: number, b: number, c: number) => void;
readonly reputationscore_isBetterThan: (a: number, b: number) => number;
readonly reputationscore_meetsMinimum: (a: number, b: number, c: number, d: bigint) => number;
readonly reputationscore_new: (a: number, b: number, c: bigint) => number;
readonly reputationscore_newWithTracking: (a: bigint, b: bigint, c: bigint, d: bigint, e: bigint) => number;
readonly reputationscore_recordFailure: (a: number) => void;
readonly reputationscore_recordSuccess: (a: number) => void;
readonly reputationscore_stake: (a: number) => bigint;
readonly reputationscore_stakeWeight: (a: number) => number;
readonly reputationscore_tasksCompleted: (a: number) => bigint;
readonly reputationscore_tasksFailed: (a: number) => bigint;
readonly reputationscore_tierName: (a: number, b: number) => void;
readonly reputationscore_toJson: (a: number, b: number) => void;
readonly reputationscore_totalTasks: (a: number) => bigint;
readonly reputationscore_updateStake: (a: number, b: bigint) => void;
readonly reputationscore_updateUptime: (a: number, b: bigint, c: bigint) => void;
readonly reputationscore_uptime: (a: number) => number;
readonly stake_weight: (a: bigint) => number;
readonly stakemanager_delegate: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
readonly stakemanager_exportJson: (a: number, b: number) => void;
readonly stakemanager_getDelegatorCount: (a: number, b: number, c: number) => number;
readonly stakemanager_getEffectiveStake: (a: number, b: number, c: number) => bigint;
readonly stakemanager_getLockTimestamp: (a: number, b: number, c: number) => bigint;
readonly stakemanager_getNodeTotalSlashed: (a: number, b: number, c: number) => bigint;
readonly stakemanager_getSlashCount: (a: number, b: number, c: number) => number;
readonly stakemanager_getStake: (a: number, b: number, c: number) => bigint;
readonly stakemanager_isLocked: (a: number, b: number, c: number) => number;
readonly stakemanager_meetsMinimum: (a: number, b: number, c: number) => number;
readonly stakemanager_new: () => number;
readonly stakemanager_newWithParams: (a: bigint, b: bigint) => number;
readonly stakemanager_slash: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
readonly stakemanager_stake: (a: number, b: number, c: number, d: number, e: bigint) => void;
readonly stakemanager_stakerCount: (a: number) => number;
readonly stakemanager_totalSlashed: (a: number) => bigint;
readonly stakemanager_totalStaked: (a: number) => bigint;
readonly stakemanager_undelegate: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
readonly stakemanager_unstake: (a: number, b: number, c: number, d: number, e: bigint) => void;
readonly version: (a: number) => void;
readonly init_panic_hook: () => void;
readonly stakemanager_minStake: (a: number) => bigint;
readonly __wbindgen_export: (a: number, b: number, c: number) => void;
readonly __wbindgen_export2: (a: number, b: number) => number;
readonly __wbindgen_export3: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_start: () => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
*
* @returns {InitOutput}
*/
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const __wbg_creditledger_free: (a: number, b: number) => void;
export const __wbg_reputationscore_free: (a: number, b: number) => void;
export const __wbg_stakemanager_free: (a: number, b: number) => void;
export const calculate_reward: (a: bigint, b: number) => bigint;
export const composite_reputation: (a: number, b: number, c: bigint) => number;
export const contribution_multiplier: (a: number) => number;
export const creditledger_balance: (a: number) => bigint;
export const creditledger_credit: (a: number, b: number, c: bigint, d: number, e: number) => void;
export const creditledger_creditWithMultiplier: (a: number, b: number, c: bigint, d: number, e: number) => void;
export const creditledger_currentMultiplier: (a: number) => number;
export const creditledger_deduct: (a: number, b: number, c: bigint) => void;
export const creditledger_eventCount: (a: number) => number;
export const creditledger_exportEarned: (a: number, b: number) => void;
export const creditledger_exportSpent: (a: number, b: number) => void;
export const creditledger_merge: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
export const creditledger_networkCompute: (a: number) => number;
export const creditledger_new: (a: number, b: number, c: number) => void;
export const creditledger_nodeId: (a: number, b: number) => void;
export const creditledger_refund: (a: number, b: number, c: number, d: number, e: bigint) => void;
export const creditledger_slash: (a: number, b: number, c: bigint) => void;
export const creditledger_stake: (a: number, b: number, c: bigint) => void;
export const creditledger_stakedAmount: (a: number) => bigint;
export const creditledger_stateRoot: (a: number, b: number) => void;
export const creditledger_stateRootHex: (a: number, b: number) => void;
export const creditledger_totalEarned: (a: number) => bigint;
export const creditledger_totalSpent: (a: number) => bigint;
export const creditledger_unstake: (a: number, b: number, c: bigint) => void;
export const creditledger_updateNetworkCompute: (a: number, b: number) => void;
export const creditledger_verifyStateRoot: (a: number, b: number, c: number) => number;
export const get_tier_name: (a: number, b: number) => void;
export const get_tiers_json: (a: number) => void;
export const reputationscore_accuracy: (a: number) => number;
export const reputationscore_compositeScore: (a: number) => number;
export const reputationscore_fromJson: (a: number, b: number, c: number) => void;
export const reputationscore_isBetterThan: (a: number, b: number) => number;
export const reputationscore_meetsMinimum: (a: number, b: number, c: number, d: bigint) => number;
export const reputationscore_new: (a: number, b: number, c: bigint) => number;
export const reputationscore_newWithTracking: (a: bigint, b: bigint, c: bigint, d: bigint, e: bigint) => number;
export const reputationscore_recordFailure: (a: number) => void;
export const reputationscore_recordSuccess: (a: number) => void;
export const reputationscore_stake: (a: number) => bigint;
export const reputationscore_stakeWeight: (a: number) => number;
export const reputationscore_tasksCompleted: (a: number) => bigint;
export const reputationscore_tasksFailed: (a: number) => bigint;
export const reputationscore_tierName: (a: number, b: number) => void;
export const reputationscore_toJson: (a: number, b: number) => void;
export const reputationscore_totalTasks: (a: number) => bigint;
export const reputationscore_updateStake: (a: number, b: bigint) => void;
export const reputationscore_updateUptime: (a: number, b: bigint, c: bigint) => void;
export const reputationscore_uptime: (a: number) => number;
export const stake_weight: (a: bigint) => number;
export const stakemanager_delegate: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
export const stakemanager_exportJson: (a: number, b: number) => void;
export const stakemanager_getDelegatorCount: (a: number, b: number, c: number) => number;
export const stakemanager_getEffectiveStake: (a: number, b: number, c: number) => bigint;
export const stakemanager_getLockTimestamp: (a: number, b: number, c: number) => bigint;
export const stakemanager_getNodeTotalSlashed: (a: number, b: number, c: number) => bigint;
export const stakemanager_getSlashCount: (a: number, b: number, c: number) => number;
export const stakemanager_getStake: (a: number, b: number, c: number) => bigint;
export const stakemanager_isLocked: (a: number, b: number, c: number) => number;
export const stakemanager_meetsMinimum: (a: number, b: number, c: number) => number;
export const stakemanager_new: () => number;
export const stakemanager_newWithParams: (a: bigint, b: bigint) => number;
export const stakemanager_slash: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
export const stakemanager_stake: (a: number, b: number, c: number, d: number, e: bigint) => void;
export const stakemanager_stakerCount: (a: number) => number;
export const stakemanager_totalSlashed: (a: number) => bigint;
export const stakemanager_totalStaked: (a: number) => bigint;
export const stakemanager_undelegate: (a: number, b: number, c: number, d: number, e: number, f: number, g: bigint) => void;
export const stakemanager_unstake: (a: number, b: number, c: number, d: number, e: bigint) => void;
export const version: (a: number) => void;
export const init_panic_hook: () => void;
export const stakemanager_minStake: (a: number) => bigint;
export const __wbindgen_export: (a: number, b: number, c: number) => void;
export const __wbindgen_export2: (a: number, b: number) => number;
export const __wbindgen_export3: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_add_to_stack_pointer: (a: number) => number;
export const __wbindgen_start: () => void;

View File

@@ -0,0 +1,245 @@
//! Contribution Curve for Early Adopter Rewards
//!
//! Implements an exponential decay curve that rewards early network participants
//! with higher multipliers that decay as the network grows.
//!
//! ```text
//! Multiplier
//! 10x |*
//! | *
//! 8x | *
//! | *
//! 6x | *
//! | *
//! 4x | *
//! | **
//! 2x | ***
//! | *****
//! 1x | ****************************
//! +--+--+--+--+--+--+--+--+--+--+--+--+--+--+---> Network Compute (M hours)
//! 0 1 2 3 4 5 6 7 8 9 10
//! ```
use wasm_bindgen::prelude::*;
/// Contribution curve calculator for early adopter rewards
///
/// The multiplier follows an exponential decay formula:
/// ```text
/// multiplier = 1 + (MAX_BONUS - 1) * e^(-network_compute / DECAY_CONSTANT)
/// ```
///
/// This ensures:
/// - Genesis contributors (0 compute) get MAX_BONUS (10x)
/// - At DECAY_CONSTANT compute hours, bonus is ~37% remaining (~4.3x)
/// - At very high compute, approaches baseline (1x)
/// - Never goes below 1x
pub struct ContributionCurve;
impl ContributionCurve {
/// Maximum multiplier for genesis contributors
pub const MAX_BONUS: f32 = 10.0;
/// Decay constant in CPU-hours (half-life of bonus decay)
pub const DECAY_CONSTANT: f64 = 1_000_000.0;
/// Calculate current multiplier based on total network compute
///
/// # Arguments
/// * `network_compute_hours` - Total CPU-hours contributed to the network
///
/// # Returns
/// A multiplier between 1.0 (baseline) and MAX_BONUS (genesis)
///
/// # Example
/// ```
/// use ruvector_economy_wasm::ContributionCurve;
///
/// // Genesis: 10x multiplier
/// assert!((ContributionCurve::current_multiplier(0.0) - 10.0).abs() < 0.01);
///
/// // At 1M hours: ~4.3x multiplier
/// let mult = ContributionCurve::current_multiplier(1_000_000.0);
/// assert!(mult > 4.0 && mult < 4.5);
///
/// // At 10M hours: ~1.0x multiplier
/// let mult = ContributionCurve::current_multiplier(10_000_000.0);
/// assert!(mult < 1.1);
/// ```
pub fn current_multiplier(network_compute_hours: f64) -> f32 {
let decay = (-network_compute_hours / Self::DECAY_CONSTANT).exp();
1.0 + (Self::MAX_BONUS - 1.0) * decay as f32
}
/// Calculate reward with multiplier applied
///
/// # Arguments
/// * `base_reward` - Base reward amount before multiplier
/// * `network_compute_hours` - Total network compute for multiplier calculation
///
/// # Returns
/// The reward amount with multiplier applied
pub fn calculate_reward(base_reward: u64, network_compute_hours: f64) -> u64 {
let multiplier = Self::current_multiplier(network_compute_hours);
(base_reward as f32 * multiplier) as u64
}
/// Get multiplier tier information for UI display
///
/// Returns a vector of (compute_hours, multiplier) tuples representing
/// key milestones in the contribution curve.
pub fn get_tiers() -> Vec<(f64, f32)> {
vec![
(0.0, 10.0),
(100_000.0, 9.1),
(500_000.0, 6.1),
(1_000_000.0, 4.3),
(2_000_000.0, 2.6),
(5_000_000.0, 1.4),
(10_000_000.0, 1.0),
]
}
/// Get the tier name based on network compute level
pub fn get_tier_name(network_compute_hours: f64) -> &'static str {
if network_compute_hours < 100_000.0 {
"Genesis"
} else if network_compute_hours < 500_000.0 {
"Pioneer"
} else if network_compute_hours < 1_000_000.0 {
"Early Adopter"
} else if network_compute_hours < 5_000_000.0 {
"Established"
} else {
"Baseline"
}
}
/// Calculate time remaining until next tier
///
/// # Arguments
/// * `current_compute` - Current network compute hours
/// * `hourly_growth` - Estimated hourly compute growth rate
///
/// # Returns
/// Hours until next tier boundary, or None if at baseline
pub fn hours_until_next_tier(current_compute: f64, hourly_growth: f64) -> Option<f64> {
if hourly_growth <= 0.0 {
return None;
}
let tiers = Self::get_tiers();
for (threshold, _) in &tiers {
if current_compute < *threshold {
return Some((*threshold - current_compute) / hourly_growth);
}
}
None // Already at baseline
}
}
/// Calculate contribution multiplier (WASM export)
///
/// Returns the reward multiplier based on total network compute hours.
/// Early adopters get up to 10x rewards, decaying to 1x as network grows.
#[wasm_bindgen]
pub fn contribution_multiplier(network_compute_hours: f64) -> f32 {
ContributionCurve::current_multiplier(network_compute_hours)
}
/// Calculate reward with multiplier (WASM export)
#[wasm_bindgen]
pub fn calculate_reward(base_reward: u64, network_compute_hours: f64) -> u64 {
ContributionCurve::calculate_reward(base_reward, network_compute_hours)
}
/// Get tier name based on compute level (WASM export)
#[wasm_bindgen]
pub fn get_tier_name(network_compute_hours: f64) -> String {
ContributionCurve::get_tier_name(network_compute_hours).to_string()
}
/// Get tier information as JSON (WASM export)
#[wasm_bindgen]
pub fn get_tiers_json() -> String {
let tiers = ContributionCurve::get_tiers();
let tier_objs: Vec<_> = tiers
.iter()
.map(|(hours, mult)| format!(r#"{{"hours":{},"multiplier":{:.1}}}"#, hours, mult))
.collect();
format!("[{}]", tier_objs.join(","))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_genesis_multiplier() {
let mult = ContributionCurve::current_multiplier(0.0);
assert!(
(mult - 10.0).abs() < 0.01,
"Genesis should give 10x, got {}",
mult
);
}
#[test]
fn test_decay_constant_multiplier() {
// At decay constant, e^(-1) ~= 0.368
// So multiplier = 1 + 9 * 0.368 = 4.31
let mult = ContributionCurve::current_multiplier(1_000_000.0);
assert!(
mult > 4.0 && mult < 4.5,
"At decay constant should be ~4.3x, got {}",
mult
);
}
#[test]
fn test_high_compute_baseline() {
let mult = ContributionCurve::current_multiplier(10_000_000.0);
assert!(mult < 1.1, "High compute should approach 1x, got {}", mult);
}
#[test]
fn test_multiplier_never_below_one() {
let mult = ContributionCurve::current_multiplier(100_000_000.0);
assert!(
mult >= 1.0,
"Multiplier should never go below 1, got {}",
mult
);
}
#[test]
fn test_calculate_reward() {
let base = 100;
let reward = ContributionCurve::calculate_reward(base, 0.0);
assert_eq!(
reward, 1000,
"Genesis 100 base should give 1000, got {}",
reward
);
}
#[test]
fn test_tier_names() {
assert_eq!(ContributionCurve::get_tier_name(0.0), "Genesis");
assert_eq!(ContributionCurve::get_tier_name(100_000.0), "Pioneer");
assert_eq!(ContributionCurve::get_tier_name(500_000.0), "Early Adopter");
assert_eq!(ContributionCurve::get_tier_name(1_000_000.0), "Established");
assert_eq!(ContributionCurve::get_tier_name(10_000_000.0), "Baseline");
}
#[test]
fn test_wasm_export_functions() {
assert!((contribution_multiplier(0.0) - 10.0).abs() < 0.01);
assert_eq!(calculate_reward(100, 0.0), 1000);
assert_eq!(get_tier_name(0.0), "Genesis");
assert!(get_tiers_json().contains("Genesis") == false); // JSON format
assert!(get_tiers_json().starts_with("["));
}
}

View File

@@ -0,0 +1,493 @@
//! CRDT-based Credit Ledger
//!
//! Implements a conflict-free replicated data type (CRDT) ledger for P2P consistency.
//! Uses G-Counters for earnings (monotonically increasing) and PN-Counters for spending.
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use wasm_bindgen::prelude::*;
use crate::curve::ContributionCurve;
/// Get current timestamp in milliseconds (works in both WASM and native)
fn current_timestamp_ms() -> u64 {
#[cfg(target_arch = "wasm32")]
{
js_sys::Date::now() as u64
}
#[cfg(not(target_arch = "wasm32"))]
{
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
}
/// Credit event reasons for audit trail
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub enum CreditReason {
/// Earned from completing a task
TaskCompleted { task_id: String },
/// Earned from uptime bonus
UptimeReward { hours: f32 },
/// Earned from referral
Referral { referee: String },
/// Staked for participation
Stake { amount: u64, locked: bool },
/// Transferred between nodes
Transfer {
from: String,
to: String,
memo: String,
},
/// Penalty for invalid work
Penalty { reason: String },
}
/// CRDT-based credit ledger for P2P consistency
///
/// The ledger uses two types of counters:
/// - G-Counter (grow-only) for credits earned - safe for concurrent updates
/// - PN-Counter (positive-negative) for credits spent - supports disputes
///
/// ```text
/// Earned (G-Counter): Spent (PN-Counter):
/// +----------------+ +--------------------+
/// | event_1: 100 | | event_a: (50, 0) | <- (positive, negative)
/// | event_2: 200 | | event_b: (30, 10) | <- disputed 10 returned
/// | event_3: 150 | +--------------------+
/// +----------------+
///
/// Balance = sum(earned) - sum(spent.positive - spent.negative) - staked
/// ```
#[wasm_bindgen]
#[derive(Clone)]
pub struct CreditLedger {
/// Node identifier
node_id: String,
/// G-Counter: monotonically increasing credits earned
/// Key: event_id, Value: amount credited
earned: FxHashMap<String, u64>,
/// PN-Counter: credits spent/penalized
/// Key: event_id, Value: (positive_spent, negative_refund)
spent: FxHashMap<String, (u64, u64)>,
/// Merkle root of current state for quick verification
state_root: [u8; 32],
/// Total network compute hours (for multiplier calculation)
network_compute: f64,
/// Staked credits (locked for participation)
staked: u64,
/// Last sync timestamp (Unix ms)
last_sync: u64,
/// Event counter for generating unique IDs
event_counter: u64,
}
#[wasm_bindgen]
impl CreditLedger {
/// Create a new credit ledger for a node
#[wasm_bindgen(constructor)]
pub fn new(node_id: String) -> Result<CreditLedger, JsValue> {
if node_id.is_empty() {
return Err(JsValue::from_str("Node ID cannot be empty"));
}
Ok(CreditLedger {
node_id,
earned: FxHashMap::default(),
spent: FxHashMap::default(),
state_root: [0u8; 32],
network_compute: 0.0,
staked: 0,
last_sync: 0,
event_counter: 0,
})
}
/// Get the node ID
#[wasm_bindgen(js_name = nodeId)]
pub fn node_id(&self) -> String {
self.node_id.clone()
}
/// Get current available balance (earned - spent - staked)
#[wasm_bindgen]
pub fn balance(&self) -> u64 {
let total_earned: u64 = self.earned.values().sum();
let total_spent: u64 = self
.spent
.values()
.map(|(pos, neg)| pos.saturating_sub(*neg))
.sum();
total_earned
.saturating_sub(total_spent)
.saturating_sub(self.staked)
}
/// Get total credits ever earned (before spending)
#[wasm_bindgen(js_name = totalEarned)]
pub fn total_earned(&self) -> u64 {
self.earned.values().sum()
}
/// Get total credits spent
#[wasm_bindgen(js_name = totalSpent)]
pub fn total_spent(&self) -> u64 {
self.spent
.values()
.map(|(pos, neg)| pos.saturating_sub(*neg))
.sum()
}
/// Get staked amount
#[wasm_bindgen(js_name = stakedAmount)]
pub fn staked_amount(&self) -> u64 {
self.staked
}
/// Get network compute hours
#[wasm_bindgen(js_name = networkCompute)]
pub fn network_compute(&self) -> f64 {
self.network_compute
}
/// Get current contribution multiplier
#[wasm_bindgen(js_name = currentMultiplier)]
pub fn current_multiplier(&self) -> f32 {
ContributionCurve::current_multiplier(self.network_compute)
}
/// Get the state root (Merkle root of ledger state)
#[wasm_bindgen(js_name = stateRoot)]
pub fn state_root(&self) -> Vec<u8> {
self.state_root.to_vec()
}
/// Get state root as hex string
#[wasm_bindgen(js_name = stateRootHex)]
pub fn state_root_hex(&self) -> String {
self.state_root
.iter()
.map(|b| format!("{:02x}", b))
.collect()
}
/// Credit the ledger (earn credits)
///
/// This updates the G-Counter which is monotonically increasing.
/// Safe for concurrent P2P updates.
#[wasm_bindgen]
pub fn credit(&mut self, amount: u64, _reason: &str) -> Result<String, JsValue> {
if amount == 0 {
return Err(JsValue::from_str("Amount must be positive"));
}
// Generate unique event ID
self.event_counter += 1;
let event_id = format!("{}:{}", self.node_id, self.event_counter);
// Update G-Counter
self.earned.insert(event_id.clone(), amount);
// Update state root
self.recompute_state_root();
Ok(event_id)
}
/// Credit with multiplier applied (for task rewards)
#[wasm_bindgen(js_name = creditWithMultiplier)]
pub fn credit_with_multiplier(
&mut self,
base_amount: u64,
reason: &str,
) -> Result<String, JsValue> {
let multiplier = self.current_multiplier();
let amount = (base_amount as f32 * multiplier) as u64;
self.credit(amount, reason)
}
/// Deduct from the ledger (spend credits)
///
/// This updates the PN-Counter positive side.
/// Spending can be disputed/refunded by updating the negative side.
#[wasm_bindgen]
pub fn deduct(&mut self, amount: u64) -> Result<String, JsValue> {
if self.balance() < amount {
return Err(JsValue::from_str("Insufficient balance"));
}
// Generate unique event ID
self.event_counter += 1;
let event_id = format!("{}:{}", self.node_id, self.event_counter);
// Update PN-Counter (positive side)
self.spent.insert(event_id.clone(), (amount, 0));
// Update state root
self.recompute_state_root();
Ok(event_id)
}
/// Refund a previous deduction (dispute resolution)
///
/// This updates the PN-Counter negative side for the given event.
#[wasm_bindgen]
pub fn refund(&mut self, event_id: &str, amount: u64) -> Result<(), JsValue> {
let entry = self
.spent
.get_mut(event_id)
.ok_or_else(|| JsValue::from_str("Event not found"))?;
if entry.1 + amount > entry.0 {
return Err(JsValue::from_str("Refund exceeds original spend"));
}
entry.1 += amount;
self.recompute_state_root();
Ok(())
}
/// Stake credits for participation
#[wasm_bindgen]
pub fn stake(&mut self, amount: u64) -> Result<(), JsValue> {
if self.balance() < amount {
return Err(JsValue::from_str("Insufficient balance for stake"));
}
self.staked += amount;
self.recompute_state_root();
Ok(())
}
/// Unstake credits
#[wasm_bindgen]
pub fn unstake(&mut self, amount: u64) -> Result<(), JsValue> {
if self.staked < amount {
return Err(JsValue::from_str("Insufficient staked amount"));
}
self.staked -= amount;
self.recompute_state_root();
Ok(())
}
/// Slash staked credits (penalty for bad behavior)
///
/// Returns the actual amount slashed (may be less if stake is insufficient)
#[wasm_bindgen]
pub fn slash(&mut self, amount: u64) -> Result<u64, JsValue> {
let slash_amount = amount.min(self.staked);
self.staked -= slash_amount;
self.recompute_state_root();
Ok(slash_amount)
}
/// Update network compute hours (from P2P sync)
#[wasm_bindgen(js_name = updateNetworkCompute)]
pub fn update_network_compute(&mut self, hours: f64) {
self.network_compute = hours;
}
/// Merge with another ledger (CRDT merge operation)
///
/// This is the core CRDT operation - associative, commutative, and idempotent.
/// Safe to apply in any order with any number of concurrent updates.
#[wasm_bindgen]
pub fn merge(&mut self, other_earned: &[u8], other_spent: &[u8]) -> Result<u32, JsValue> {
let mut merged_count = 0u32;
// Deserialize and merge earned counter (G-Counter: take max)
let earned_map: FxHashMap<String, u64> = serde_json::from_slice(other_earned)
.map_err(|e| JsValue::from_str(&format!("Failed to parse earned: {}", e)))?;
for (key, value) in earned_map {
let entry = self.earned.entry(key).or_insert(0);
if value > *entry {
*entry = value;
merged_count += 1;
}
}
// Deserialize and merge spent counter (PN-Counter: take max of each component)
let spent_map: FxHashMap<String, (u64, u64)> = serde_json::from_slice(other_spent)
.map_err(|e| JsValue::from_str(&format!("Failed to parse spent: {}", e)))?;
for (key, (pos, neg)) in spent_map {
let entry = self.spent.entry(key).or_insert((0, 0));
if pos > entry.0 || neg > entry.1 {
entry.0 = entry.0.max(pos);
entry.1 = entry.1.max(neg);
merged_count += 1;
}
}
// Update state and timestamp
self.recompute_state_root();
self.last_sync = current_timestamp_ms();
Ok(merged_count)
}
/// Export earned counter for P2P sync
#[wasm_bindgen(js_name = exportEarned)]
pub fn export_earned(&self) -> Result<Vec<u8>, JsValue> {
serde_json::to_vec(&self.earned)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}
/// Export spent counter for P2P sync
#[wasm_bindgen(js_name = exportSpent)]
pub fn export_spent(&self) -> Result<Vec<u8>, JsValue> {
serde_json::to_vec(&self.spent)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}
/// Get event count
#[wasm_bindgen(js_name = eventCount)]
pub fn event_count(&self) -> usize {
self.earned.len() + self.spent.len()
}
/// Verify state root matches current state
#[wasm_bindgen(js_name = verifyStateRoot)]
pub fn verify_state_root(&self, expected_root: &[u8]) -> bool {
if expected_root.len() != 32 {
return false;
}
let mut expected = [0u8; 32];
expected.copy_from_slice(expected_root);
self.state_root == expected
}
/// Recompute the Merkle state root
fn recompute_state_root(&mut self) {
let mut hasher = Sha256::new();
// Hash earned entries (sorted for determinism)
let mut earned_keys: Vec<_> = self.earned.keys().collect();
earned_keys.sort();
for key in earned_keys {
hasher.update(key.as_bytes());
hasher.update(&self.earned[key].to_le_bytes());
}
// Hash spent entries (sorted for determinism)
let mut spent_keys: Vec<_> = self.spent.keys().collect();
spent_keys.sort();
for key in spent_keys {
let (pos, neg) = self.spent[key];
hasher.update(key.as_bytes());
hasher.update(&pos.to_le_bytes());
hasher.update(&neg.to_le_bytes());
}
// Hash staked amount
hasher.update(&self.staked.to_le_bytes());
// Finalize
self.state_root = hasher.finalize().into();
}
}
#[cfg(test)]
mod tests {
// All ledger tests require JsValue which only works in WASM
// Native tests are in curve.rs and reputation.rs
#[cfg(target_arch = "wasm32")]
mod wasm_tests {
use super::super::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_ledger_creation() {
let ledger = CreditLedger::new("node-1".to_string()).unwrap();
assert_eq!(ledger.node_id(), "node-1");
assert_eq!(ledger.balance(), 0);
}
#[wasm_bindgen_test]
fn test_empty_node_id_rejected() {
let result = CreditLedger::new("".to_string());
assert!(result.is_err());
}
#[wasm_bindgen_test]
fn test_credit_and_deduct() {
let mut ledger = CreditLedger::new("node-1".to_string()).unwrap();
ledger.credit(100, "task:1").unwrap();
assert_eq!(ledger.balance(), 100);
assert_eq!(ledger.total_earned(), 100);
ledger.deduct(30).unwrap();
assert_eq!(ledger.balance(), 70);
assert_eq!(ledger.total_spent(), 30);
}
#[wasm_bindgen_test]
fn test_insufficient_balance() {
let mut ledger = CreditLedger::new("node-1".to_string()).unwrap();
ledger.credit(50, "task:1").unwrap();
let result = ledger.deduct(100);
assert!(result.is_err());
}
#[wasm_bindgen_test]
fn test_stake_and_slash() {
let mut ledger = CreditLedger::new("node-1".to_string()).unwrap();
ledger.credit(200, "task:1").unwrap();
ledger.stake(100).unwrap();
assert_eq!(ledger.balance(), 100);
assert_eq!(ledger.staked_amount(), 100);
let slashed = ledger.slash(30).unwrap();
assert_eq!(slashed, 30);
assert_eq!(ledger.staked_amount(), 70);
}
#[wasm_bindgen_test]
fn test_refund() {
let mut ledger = CreditLedger::new("node-1".to_string()).unwrap();
ledger.credit(100, "task:1").unwrap();
let event_id = ledger.deduct(50).unwrap();
assert_eq!(ledger.balance(), 50);
ledger.refund(&event_id, 20).unwrap();
assert_eq!(ledger.balance(), 70);
}
#[wasm_bindgen_test]
fn test_state_root_changes() {
let mut ledger = CreditLedger::new("node-1".to_string()).unwrap();
let initial_root = ledger.state_root();
ledger.credit(100, "task:1").unwrap();
let after_credit = ledger.state_root();
assert_ne!(initial_root, after_credit);
}
}
}

View File

@@ -0,0 +1,92 @@
//! # ruvector-economy-wasm
//!
//! A CRDT-based autonomous credit economy for distributed compute networks.
//! Designed for WASM execution with P2P consistency guarantees.
//!
//! ## Features
//!
//! - **CRDT Ledger**: G-Counter and PN-Counter for P2P-safe credit tracking
//! - **Contribution Curve**: 10x early adopter multiplier decaying to 1x baseline
//! - **Stake/Slash Mechanics**: Participation requirements with slashing for bad actors
//! - **Reputation Scoring**: Multi-factor reputation based on accuracy, uptime, and stake
//! - **Merkle Verification**: State root for quick ledger verification
//!
//! ## Quick Start (JavaScript)
//!
//! ```javascript
//! import { CreditLedger, ReputationScore, contribution_multiplier } from '@ruvector/economy-wasm';
//!
//! // Create a new ledger for a node
//! const ledger = new CreditLedger("node-123");
//!
//! // Earn credits
//! ledger.credit(100, "task:abc");
//! console.log(`Balance: ${ledger.balance()}`);
//!
//! // Check multiplier for early adopters
//! const mult = contribution_multiplier(50000.0); // 50K network compute hours
//! console.log(`Multiplier: ${mult}x`); // ~8.5x
//!
//! // Track reputation
//! const rep = new ReputationScore(0.95, 0.98, 1000);
//! console.log(`Composite score: ${rep.composite_score()}`);
//! ```
//!
//! ## Architecture
//!
//! ```text
//! +------------------------+
//! | CreditLedger | <-- CRDT-based P2P-safe ledger
//! | +------------------+ |
//! | | G-Counter: Earned| | <-- Monotonically increasing
//! | | PN-Counter: Spent| | <-- Can handle disputes
//! | | Stake: Locked | | <-- Participation requirement
//! | | State Root | | <-- Merkle root for verification
//! | +------------------+ |
//! +------------------------+
//! |
//! v
//! +------------------------+
//! | ContributionCurve | <-- Exponential decay: 10x -> 1x
//! +------------------------+
//! |
//! v
//! +------------------------+
//! | ReputationScore | <-- accuracy * uptime * stake_weight
//! +------------------------+
//! ```
use wasm_bindgen::prelude::*;
pub mod curve;
pub mod ledger;
pub mod reputation;
pub mod stake;
pub use curve::{contribution_multiplier, ContributionCurve};
pub use ledger::CreditLedger;
pub use reputation::ReputationScore;
pub use stake::{SlashReason, StakeManager};
/// Initialize panic hook for better error messages in console
#[wasm_bindgen(start)]
pub fn init_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
/// Get the current version of the economy module
#[wasm_bindgen]
pub fn version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version() {
assert_eq!(version(), "0.1.0");
}
}

View File

@@ -0,0 +1,375 @@
//! Reputation Scoring System
//!
//! Multi-factor reputation based on:
//! - Accuracy: Success rate of completed tasks
//! - Uptime: Availability and reliability
//! - Stake: Skin in the game (economic commitment)
//!
//! The composite score determines task priority and trust level.
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
/// Reputation score for a network participant
///
/// Combines multiple factors into a single trust score:
/// - accuracy: 0.0 to 1.0 (success rate of verified tasks)
/// - uptime: 0.0 to 1.0 (availability ratio)
/// - stake: absolute stake amount (economic commitment)
///
/// The composite score is weighted:
/// ```text
/// composite = accuracy^2 * uptime * stake_weight
///
/// where stake_weight = min(1.0, log10(stake + 1) / 6)
/// ```
///
/// This ensures:
/// - Accuracy is most important (squared)
/// - Uptime provides linear scaling
/// - Stake has diminishing returns (log scale)
#[wasm_bindgen]
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub struct ReputationScore {
/// Task success rate (0.0 - 1.0)
accuracy: f32,
/// Network availability (0.0 - 1.0)
uptime: f32,
/// Staked credits
stake: u64,
/// Number of completed tasks
tasks_completed: u64,
/// Number of failed/disputed tasks
tasks_failed: u64,
/// Total uptime in seconds
uptime_seconds: u64,
/// Total possible uptime in seconds (since registration)
total_seconds: u64,
}
#[wasm_bindgen]
impl ReputationScore {
/// Create a new reputation score
#[wasm_bindgen(constructor)]
pub fn new(accuracy: f32, uptime: f32, stake: u64) -> ReputationScore {
ReputationScore {
accuracy: accuracy.clamp(0.0, 1.0),
uptime: uptime.clamp(0.0, 1.0),
stake,
tasks_completed: 0,
tasks_failed: 0,
uptime_seconds: 0,
total_seconds: 0,
}
}
/// Create with detailed tracking
#[wasm_bindgen(js_name = newWithTracking)]
pub fn new_with_tracking(
tasks_completed: u64,
tasks_failed: u64,
uptime_seconds: u64,
total_seconds: u64,
stake: u64,
) -> ReputationScore {
let accuracy = if tasks_completed + tasks_failed > 0 {
tasks_completed as f32 / (tasks_completed + tasks_failed) as f32
} else {
0.0
};
let uptime = if total_seconds > 0 {
(uptime_seconds as f32 / total_seconds as f32).min(1.0)
} else {
0.0
};
ReputationScore {
accuracy,
uptime,
stake,
tasks_completed,
tasks_failed,
uptime_seconds,
total_seconds,
}
}
/// Get accuracy score (0.0 - 1.0)
#[wasm_bindgen(getter)]
pub fn accuracy(&self) -> f32 {
self.accuracy
}
/// Get uptime score (0.0 - 1.0)
#[wasm_bindgen(getter)]
pub fn uptime(&self) -> f32 {
self.uptime
}
/// Get stake amount
#[wasm_bindgen(getter)]
pub fn stake(&self) -> u64 {
self.stake
}
/// Calculate stake weight using logarithmic scaling
///
/// Uses log10(stake + 1) / 6 capped at 1.0
/// This means:
/// - 0 stake = 0.0 weight
/// - 100 stake = ~0.33 weight
/// - 10,000 stake = ~0.67 weight
/// - 1,000,000 stake = 1.0 weight (capped)
#[wasm_bindgen(js_name = stakeWeight)]
pub fn stake_weight(&self) -> f32 {
if self.stake == 0 {
return 0.0;
}
let log_stake = (self.stake as f64 + 1.0).log10();
(log_stake / 6.0).min(1.0) as f32
}
/// Calculate composite reputation score
///
/// Formula: accuracy^2 * uptime * stake_weight
///
/// Returns a value between 0.0 and 1.0
#[wasm_bindgen(js_name = compositeScore)]
pub fn composite_score(&self) -> f32 {
self.accuracy.powi(2) * self.uptime * self.stake_weight()
}
/// Get reputation tier based on composite score
#[wasm_bindgen(js_name = tierName)]
pub fn tier_name(&self) -> String {
let score = self.composite_score();
if score >= 0.9 {
"Diamond".to_string()
} else if score >= 0.75 {
"Platinum".to_string()
} else if score >= 0.5 {
"Gold".to_string()
} else if score >= 0.25 {
"Silver".to_string()
} else if score >= 0.1 {
"Bronze".to_string()
} else {
"Newcomer".to_string()
}
}
/// Check if node meets minimum reputation for participation
#[wasm_bindgen(js_name = meetsMinimum)]
pub fn meets_minimum(&self, min_accuracy: f32, min_uptime: f32, min_stake: u64) -> bool {
self.accuracy >= min_accuracy && self.uptime >= min_uptime && self.stake >= min_stake
}
/// Record a successful task completion
#[wasm_bindgen(js_name = recordSuccess)]
pub fn record_success(&mut self) {
self.tasks_completed += 1;
self.update_accuracy();
}
/// Record a failed/disputed task
#[wasm_bindgen(js_name = recordFailure)]
pub fn record_failure(&mut self) {
self.tasks_failed += 1;
self.update_accuracy();
}
/// Update uptime tracking
#[wasm_bindgen(js_name = updateUptime)]
pub fn update_uptime(&mut self, online_seconds: u64, total_seconds: u64) {
self.uptime_seconds = online_seconds;
self.total_seconds = total_seconds;
if total_seconds > 0 {
self.uptime = (online_seconds as f32 / total_seconds as f32).min(1.0);
}
}
/// Update stake amount
#[wasm_bindgen(js_name = updateStake)]
pub fn update_stake(&mut self, new_stake: u64) {
self.stake = new_stake;
}
/// Get tasks completed
#[wasm_bindgen(js_name = tasksCompleted)]
pub fn tasks_completed(&self) -> u64 {
self.tasks_completed
}
/// Get tasks failed
#[wasm_bindgen(js_name = tasksFailed)]
pub fn tasks_failed(&self) -> u64 {
self.tasks_failed
}
/// Get total tasks
#[wasm_bindgen(js_name = totalTasks)]
pub fn total_tasks(&self) -> u64 {
self.tasks_completed + self.tasks_failed
}
/// Check if this reputation is better than another
#[wasm_bindgen(js_name = isBetterThan)]
pub fn is_better_than(&self, other: &ReputationScore) -> bool {
self.composite_score() > other.composite_score()
}
/// Serialize to JSON
#[wasm_bindgen(js_name = toJson)]
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "{}".to_string())
}
/// Deserialize from JSON
#[wasm_bindgen(js_name = fromJson)]
pub fn from_json(json: &str) -> Result<ReputationScore, JsValue> {
serde_json::from_str(json)
.map_err(|e| JsValue::from_str(&format!("Failed to parse JSON: {}", e)))
}
/// Update accuracy from tracked counts
fn update_accuracy(&mut self) {
let total = self.tasks_completed + self.tasks_failed;
if total > 0 {
self.accuracy = self.tasks_completed as f32 / total as f32;
}
}
}
/// Calculate stake weight (WASM export)
#[wasm_bindgen]
pub fn stake_weight(stake: u64) -> f32 {
if stake == 0 {
return 0.0;
}
let log_stake = (stake as f64 + 1.0).log10();
(log_stake / 6.0).min(1.0) as f32
}
/// Calculate composite reputation score (WASM export)
#[wasm_bindgen]
pub fn composite_reputation(accuracy: f32, uptime: f32, stake: u64) -> f32 {
let rep = ReputationScore::new(accuracy, uptime, stake);
rep.composite_score()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_reputation() {
let rep = ReputationScore::new(0.95, 0.98, 1000);
assert!((rep.accuracy() - 0.95).abs() < 0.001);
assert!((rep.uptime() - 0.98).abs() < 0.001);
assert_eq!(rep.stake(), 1000);
}
#[test]
fn test_clamp_values() {
let rep = ReputationScore::new(1.5, -0.5, 100);
assert!((rep.accuracy() - 1.0).abs() < 0.001);
assert!((rep.uptime() - 0.0).abs() < 0.001);
}
#[test]
fn test_stake_weight() {
// 0 stake = 0 weight
assert_eq!(stake_weight(0), 0.0);
// 1M stake = 1.0 weight (log10(1M) = 6)
let weight = stake_weight(1_000_000);
assert!((weight - 1.0).abs() < 0.01);
// 10K stake = ~0.67 weight (log10(10K) = 4)
let weight = stake_weight(10_000);
assert!(weight > 0.6 && weight < 0.75);
}
#[test]
fn test_composite_score() {
// Perfect accuracy (1.0), perfect uptime (1.0), max stake weight
let rep = ReputationScore::new(1.0, 1.0, 1_000_000);
let score = rep.composite_score();
assert!((score - 1.0).abs() < 0.01);
// Zero accuracy = zero score
let rep_zero = ReputationScore::new(0.0, 1.0, 1_000_000);
assert!(rep_zero.composite_score() < 0.01);
}
#[test]
fn test_tier_names() {
let diamond = ReputationScore::new(1.0, 1.0, 1_000_000);
assert_eq!(diamond.tier_name(), "Diamond");
let newcomer = ReputationScore::new(0.1, 0.1, 10);
assert_eq!(newcomer.tier_name(), "Newcomer");
}
#[test]
fn test_record_success_failure() {
let mut rep = ReputationScore::new(0.5, 1.0, 1000);
rep.tasks_completed = 5;
rep.tasks_failed = 5;
rep.record_success();
assert_eq!(rep.tasks_completed(), 6);
assert!((rep.accuracy() - 6.0 / 11.0).abs() < 0.001);
rep.record_failure();
assert_eq!(rep.tasks_failed(), 6);
assert!((rep.accuracy() - 6.0 / 12.0).abs() < 0.001);
}
#[test]
fn test_meets_minimum() {
let rep = ReputationScore::new(0.95, 0.98, 1000);
assert!(rep.meets_minimum(0.9, 0.95, 500));
assert!(!rep.meets_minimum(0.99, 0.95, 500)); // Accuracy too low
assert!(!rep.meets_minimum(0.9, 0.99, 500)); // Uptime too low
assert!(!rep.meets_minimum(0.9, 0.95, 2000)); // Stake too low
}
#[test]
fn test_is_better_than() {
let better = ReputationScore::new(0.95, 0.98, 10000);
let worse = ReputationScore::new(0.8, 0.9, 1000);
assert!(better.is_better_than(&worse));
assert!(!worse.is_better_than(&better));
}
#[test]
fn test_with_tracking() {
let rep = ReputationScore::new_with_tracking(
90, // completed
10, // failed
3600, // uptime
4000, // total
5000, // stake
);
assert!((rep.accuracy() - 0.9).abs() < 0.001);
assert!((rep.uptime() - 0.9).abs() < 0.001);
assert_eq!(rep.stake(), 5000);
}
#[test]
fn test_json_serialization() {
let rep = ReputationScore::new(0.95, 0.98, 1000);
let json = rep.to_json();
assert!(json.contains("accuracy"));
let parsed = ReputationScore::from_json(&json).unwrap();
assert!((parsed.accuracy() - rep.accuracy()).abs() < 0.001);
}
}

View File

@@ -0,0 +1,456 @@
//! Stake/Slash Mechanics
//!
//! Implements participation requirements and penalty system:
//! - Minimum stake to participate in network
//! - Slash conditions for bad behavior
//! - Stake delegation support
//! - Lock periods for stability
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
/// Get current timestamp in milliseconds (works in both WASM and native)
fn current_timestamp_ms() -> u64 {
#[cfg(target_arch = "wasm32")]
{
js_sys::Date::now() as u64
}
#[cfg(not(target_arch = "wasm32"))]
{
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
}
/// Reasons for slashing stake
#[wasm_bindgen]
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum SlashReason {
/// Invalid task result
InvalidResult = 0,
/// Double-spending attempt
DoubleSpend = 1,
/// Sybil attack detected
SybilAttack = 2,
/// Excessive downtime
Downtime = 3,
/// Spam/flooding
Spam = 4,
/// Malicious behavior
Malicious = 5,
}
impl SlashReason {
/// Get slash percentage for this reason
pub fn slash_percentage(&self) -> f32 {
match self {
SlashReason::InvalidResult => 0.05, // 5% for errors
SlashReason::DoubleSpend => 1.0, // 100% for fraud
SlashReason::SybilAttack => 0.5, // 50% for sybil
SlashReason::Downtime => 0.01, // 1% for downtime
SlashReason::Spam => 0.1, // 10% for spam
SlashReason::Malicious => 0.75, // 75% for malicious
}
}
}
/// Stake entry for a node
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct StakeEntry {
/// Staked amount
pub amount: u64,
/// Lock timestamp (Unix ms) - cannot unstake before this
pub locked_until: u64,
/// Delegated stake (from other nodes)
pub delegated: u64,
/// Nodes that delegated to this one
pub delegators: Vec<String>,
/// Slash history
pub slashes: Vec<SlashEvent>,
}
/// Record of a slash event
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct SlashEvent {
/// Amount slashed
pub amount: u64,
/// Reason for slash
pub reason: SlashReason,
/// Timestamp
pub timestamp: u64,
/// Evidence (task ID, etc.)
pub evidence: String,
}
/// Stake manager for the network
#[wasm_bindgen]
pub struct StakeManager {
/// Stakes by node ID
stakes: FxHashMap<String, StakeEntry>,
/// Minimum stake to participate
min_stake: u64,
/// Default lock period in milliseconds
default_lock_period: u64,
/// Total staked across network
total_staked: u64,
/// Total slashed
total_slashed: u64,
}
#[wasm_bindgen]
impl StakeManager {
/// Create a new stake manager
#[wasm_bindgen(constructor)]
pub fn new() -> StakeManager {
StakeManager {
stakes: FxHashMap::default(),
min_stake: 100, // 100 credits minimum
default_lock_period: 86_400_000, // 24 hours in ms
total_staked: 0,
total_slashed: 0,
}
}
/// Create with custom parameters
#[wasm_bindgen(js_name = newWithParams)]
pub fn new_with_params(min_stake: u64, lock_period_ms: u64) -> StakeManager {
StakeManager {
stakes: FxHashMap::default(),
min_stake,
default_lock_period: lock_period_ms,
total_staked: 0,
total_slashed: 0,
}
}
/// Get minimum stake requirement
#[wasm_bindgen(js_name = minStake)]
pub fn min_stake(&self) -> u64 {
self.min_stake
}
/// Get total network staked
#[wasm_bindgen(js_name = totalStaked)]
pub fn total_staked(&self) -> u64 {
self.total_staked
}
/// Get total slashed
#[wasm_bindgen(js_name = totalSlashed)]
pub fn total_slashed(&self) -> u64 {
self.total_slashed
}
/// Get stake for a node
#[wasm_bindgen(js_name = getStake)]
pub fn get_stake(&self, node_id: &str) -> u64 {
self.stakes.get(node_id).map(|s| s.amount).unwrap_or(0)
}
/// Get effective stake (own + delegated)
#[wasm_bindgen(js_name = getEffectiveStake)]
pub fn get_effective_stake(&self, node_id: &str) -> u64 {
self.stakes
.get(node_id)
.map(|s| s.amount + s.delegated)
.unwrap_or(0)
}
/// Check if node meets minimum stake
#[wasm_bindgen(js_name = meetsMinimum)]
pub fn meets_minimum(&self, node_id: &str) -> bool {
self.get_effective_stake(node_id) >= self.min_stake
}
/// Stake credits for a node
#[wasm_bindgen]
pub fn stake(&mut self, node_id: &str, amount: u64) -> Result<(), JsValue> {
if amount == 0 {
return Err(JsValue::from_str("Amount must be positive"));
}
let now = current_timestamp_ms();
let locked_until = now + self.default_lock_period;
let entry = self
.stakes
.entry(node_id.to_string())
.or_insert_with(|| StakeEntry {
amount: 0,
locked_until: 0,
delegated: 0,
delegators: Vec::new(),
slashes: Vec::new(),
});
entry.amount += amount;
entry.locked_until = locked_until;
self.total_staked += amount;
Ok(())
}
/// Unstake credits (if lock period has passed)
#[wasm_bindgen]
pub fn unstake(&mut self, node_id: &str, amount: u64) -> Result<u64, JsValue> {
let now = current_timestamp_ms();
let entry = self
.stakes
.get_mut(node_id)
.ok_or_else(|| JsValue::from_str("No stake found"))?;
if now < entry.locked_until {
return Err(JsValue::from_str("Stake is locked"));
}
if amount > entry.amount {
return Err(JsValue::from_str("Insufficient stake"));
}
entry.amount -= amount;
self.total_staked -= amount;
Ok(amount)
}
/// Slash stake for bad behavior
#[wasm_bindgen]
pub fn slash(
&mut self,
node_id: &str,
reason: SlashReason,
evidence: &str,
) -> Result<u64, JsValue> {
let now = current_timestamp_ms();
let entry = self
.stakes
.get_mut(node_id)
.ok_or_else(|| JsValue::from_str("No stake found"))?;
// Calculate slash amount
let slash_pct = reason.slash_percentage();
let slash_amount = ((entry.amount as f64) * (slash_pct as f64)) as u64;
// Apply slash
entry.amount = entry.amount.saturating_sub(slash_amount);
self.total_staked -= slash_amount;
self.total_slashed += slash_amount;
// Record event
entry.slashes.push(SlashEvent {
amount: slash_amount,
reason,
timestamp: now,
evidence: evidence.to_string(),
});
Ok(slash_amount)
}
/// Delegate stake to another node
#[wasm_bindgen]
pub fn delegate(&mut self, from_node: &str, to_node: &str, amount: u64) -> Result<(), JsValue> {
// Verify from_node has sufficient stake
let from_entry = self
.stakes
.get_mut(from_node)
.ok_or_else(|| JsValue::from_str("Delegator has no stake"))?;
if from_entry.amount < amount {
return Err(JsValue::from_str("Insufficient stake to delegate"));
}
// Reduce from_node stake
from_entry.amount -= amount;
// Add to to_node delegated
let to_entry = self
.stakes
.entry(to_node.to_string())
.or_insert_with(|| StakeEntry {
amount: 0,
locked_until: 0,
delegated: 0,
delegators: Vec::new(),
slashes: Vec::new(),
});
to_entry.delegated += amount;
if !to_entry.delegators.contains(&from_node.to_string()) {
to_entry.delegators.push(from_node.to_string());
}
Ok(())
}
/// Undelegate stake
#[wasm_bindgen]
pub fn undelegate(
&mut self,
from_node: &str,
to_node: &str,
amount: u64,
) -> Result<(), JsValue> {
// Reduce delegated from to_node
let to_entry = self
.stakes
.get_mut(to_node)
.ok_or_else(|| JsValue::from_str("Target node not found"))?;
if to_entry.delegated < amount {
return Err(JsValue::from_str("Insufficient delegated amount"));
}
to_entry.delegated -= amount;
// Return to from_node
let from_entry = self
.stakes
.entry(from_node.to_string())
.or_insert_with(|| StakeEntry {
amount: 0,
locked_until: 0,
delegated: 0,
delegators: Vec::new(),
slashes: Vec::new(),
});
from_entry.amount += amount;
Ok(())
}
/// Get lock timestamp for a node
#[wasm_bindgen(js_name = getLockTimestamp)]
pub fn get_lock_timestamp(&self, node_id: &str) -> u64 {
self.stakes
.get(node_id)
.map(|s| s.locked_until)
.unwrap_or(0)
}
/// Check if stake is locked
#[wasm_bindgen(js_name = isLocked)]
pub fn is_locked(&self, node_id: &str) -> bool {
let now = current_timestamp_ms();
self.stakes
.get(node_id)
.map(|s| now < s.locked_until)
.unwrap_or(false)
}
/// Get slash count for a node
#[wasm_bindgen(js_name = getSlashCount)]
pub fn get_slash_count(&self, node_id: &str) -> usize {
self.stakes
.get(node_id)
.map(|s| s.slashes.len())
.unwrap_or(0)
}
/// Get total amount slashed from a node
#[wasm_bindgen(js_name = getNodeTotalSlashed)]
pub fn get_node_total_slashed(&self, node_id: &str) -> u64 {
self.stakes
.get(node_id)
.map(|s| s.slashes.iter().map(|e| e.amount).sum())
.unwrap_or(0)
}
/// Get delegator count
#[wasm_bindgen(js_name = getDelegatorCount)]
pub fn get_delegator_count(&self, node_id: &str) -> usize {
self.stakes
.get(node_id)
.map(|s| s.delegators.len())
.unwrap_or(0)
}
/// Get number of stakers
#[wasm_bindgen(js_name = stakerCount)]
pub fn staker_count(&self) -> usize {
self.stakes.len()
}
/// Export stake data as JSON
#[wasm_bindgen(js_name = exportJson)]
pub fn export_json(&self) -> String {
serde_json::to_string(&self.stakes).unwrap_or_else(|_| "{}".to_string())
}
}
impl Default for StakeManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slash_percentages() {
assert!((SlashReason::InvalidResult.slash_percentage() - 0.05).abs() < 0.001);
assert!((SlashReason::DoubleSpend.slash_percentage() - 1.0).abs() < 0.001);
assert!((SlashReason::SybilAttack.slash_percentage() - 0.5).abs() < 0.001);
}
#[test]
fn test_default_params() {
let manager = StakeManager::new();
assert_eq!(manager.min_stake(), 100);
}
// Tests that use JsValue-returning functions must be gated for WASM
#[cfg(target_arch = "wasm32")]
mod wasm_tests {
use super::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_stake_and_unstake() {
let mut manager = StakeManager::new();
manager.stake("node-1", 500).unwrap();
assert_eq!(manager.get_stake("node-1"), 500);
assert_eq!(manager.total_staked(), 500);
assert!(manager.meets_minimum("node-1"));
// Cannot unstake immediately (locked)
assert!(manager.unstake("node-1", 100).is_err());
}
#[wasm_bindgen_test]
fn test_slash() {
let mut manager = StakeManager::new();
manager.stake("node-1", 1000).unwrap();
// Slash for invalid result (5%)
let slashed = manager
.slash("node-1", SlashReason::InvalidResult, "task:123")
.unwrap();
assert_eq!(slashed, 50);
assert_eq!(manager.get_stake("node-1"), 950);
assert_eq!(manager.total_slashed(), 50);
}
#[wasm_bindgen_test]
fn test_delegation() {
let mut manager = StakeManager::new();
manager.stake("node-1", 1000).unwrap();
manager.delegate("node-1", "node-2", 300).unwrap();
assert_eq!(manager.get_stake("node-1"), 700);
assert_eq!(manager.get_effective_stake("node-2"), 300);
assert_eq!(manager.get_delegator_count("node-2"), 1);
}
}
}