Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
53
vendor/ruvector/crates/ruvector-economy-wasm/Cargo.toml
vendored
Normal file
53
vendor/ruvector/crates/ruvector-economy-wasm/Cargo.toml
vendored
Normal 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"]
|
||||
406
vendor/ruvector/crates/ruvector-economy-wasm/README.md
vendored
Normal file
406
vendor/ruvector/crates/ruvector-economy-wasm/README.md
vendored
Normal 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
|
||||
303
vendor/ruvector/crates/ruvector-economy-wasm/pkg/README.md
vendored
Normal file
303
vendor/ruvector/crates/ruvector-economy-wasm/pkg/README.md
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
# @ruvector/economy-wasm - CRDT Credit Economy for Distributed Compute
|
||||
|
||||
[](https://www.npmjs.com/package/ruvector-economy-wasm)
|
||||
[](https://github.com/ruvnet/ruvector)
|
||||
[](https://www.npmjs.com/package/ruvector-economy-wasm)
|
||||
[](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
|
||||
43
vendor/ruvector/crates/ruvector-economy-wasm/pkg/package.json
vendored
Normal file
43
vendor/ruvector/crates/ruvector-economy-wasm/pkg/package.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
||||
468
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm.d.ts
vendored
Normal file
468
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm.d.ts
vendored
Normal 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>;
|
||||
1414
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm.js
vendored
Normal file
1414
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm_bg.wasm
vendored
Normal file
BIN
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm_bg.wasm
vendored
Normal file
Binary file not shown.
81
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm_bg.wasm.d.ts
vendored
Normal file
81
vendor/ruvector/crates/ruvector-economy-wasm/pkg/ruvector_economy_wasm_bg.wasm.d.ts
vendored
Normal 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;
|
||||
245
vendor/ruvector/crates/ruvector-economy-wasm/src/curve.rs
vendored
Normal file
245
vendor/ruvector/crates/ruvector-economy-wasm/src/curve.rs
vendored
Normal 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("["));
|
||||
}
|
||||
}
|
||||
493
vendor/ruvector/crates/ruvector-economy-wasm/src/ledger.rs
vendored
Normal file
493
vendor/ruvector/crates/ruvector-economy-wasm/src/ledger.rs
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
vendor/ruvector/crates/ruvector-economy-wasm/src/lib.rs
vendored
Normal file
92
vendor/ruvector/crates/ruvector-economy-wasm/src/lib.rs
vendored
Normal 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");
|
||||
}
|
||||
}
|
||||
375
vendor/ruvector/crates/ruvector-economy-wasm/src/reputation.rs
vendored
Normal file
375
vendor/ruvector/crates/ruvector-economy-wasm/src/reputation.rs
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
456
vendor/ruvector/crates/ruvector-economy-wasm/src/stake.rs
vendored
Normal file
456
vendor/ruvector/crates/ruvector-economy-wasm/src/stake.rs
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user