From 4f6ddcce519380dafe01cdee0061954adfe2fdc8 Mon Sep 17 00:00:00 2001 From: Cardinal Date: Thu, 31 Jul 2025 15:36:24 +0200 Subject: [PATCH 1/3] change order, add info and docs --- tasks/compare.ts | 20 ++++++++++++++++++++ tasks/contracts.ts | 16 ++++++++++++++++ tasks/copybatch.ts | 28 ++++++++++++++++++++++++++++ tasks/deployments.ts | 13 +++++++++++++ tasks/signatures.ts | 18 ++++++++++++++++++ tasks/status.ts | 17 ++++++++++++++++- 6 files changed, 111 insertions(+), 1 deletion(-) diff --git a/tasks/compare.ts b/tasks/compare.ts index 80399a1f..1c4eb0ea 100644 --- a/tasks/compare.ts +++ b/tasks/compare.ts @@ -1,3 +1,23 @@ +/** + * Compare Task - Compare bytecodes between two deployments + * + * Usage: + * npx hardhat compare --source mainnet --target testnet # Compare mainnet vs testnet + * npx hardhat compare --source testnet --target local # Compare testnet vs local + * npx hardhat compare --source mainnet --target tenderly # Compare mainnet vs tenderly + * + * Parameters: + * --source: Source network (mainnet, testnet, local, tenderly, pretestnet) + * --target: Target network (mainnet, testnet, local, tenderly, pretestnet) + * + * This task: + * - Compares bytecodes between deployments on different networks + * - Shows which contracts are identical vs different + * - Identifies missing contracts in either deployment + * - Useful for verifying consistent deployments across networks + * - Color-coded output: ✅ identical, ❌ different, ⚠️ missing + */ + import { task } from 'hardhat/config'; import fs from 'fs'; import path from 'path'; diff --git a/tasks/contracts.ts b/tasks/contracts.ts index f8d2d367..5e5da2b3 100644 --- a/tasks/contracts.ts +++ b/tasks/contracts.ts @@ -1,3 +1,19 @@ +/** + * Contracts Task - Display contract deployment information + * + * Usage: + * npx hardhat contracts --target main # Show mainnet contracts + * npx hardhat contracts --target test # Show testnet contracts + * npx hardhat contracts --target local # Show local contracts + * npx hardhat contracts --target pretestnet # Show pretestnet contracts + * npx hardhat contracts --target tenderly # Show tenderly contracts + * + * This task displays: + * - Contract addresses for the specified network + * - Explorer URLs for each contract + * - Formatted output for easy copying + */ + import { task } from 'hardhat/config'; import fs from 'fs'; import path from 'path'; diff --git a/tasks/copybatch.ts b/tasks/copybatch.ts index 9965adf3..9dd25849 100644 --- a/tasks/copybatch.ts +++ b/tasks/copybatch.ts @@ -1,3 +1,31 @@ +/** + * Copy Batch Task - Use copyBatch function from PostageStamp contract + * + * Usage: + * npx hardhat copy \ + * --owner 0x1234... \ + * --initialbalance 1000000000000000000 \ + * --depth 20 \ + * --bucketdepth 16 \ + * --batchid 0xabcd... \ + * --immutable false \ + * --contract 0x5678... + * + * Parameters: + * --owner: The account's address + * --initialbalance: Initial balance for the batch + * --depth: Batch depth + * --bucketdepth: Bucket depth + * --batchid: Batch ID + * --immutable: Whether batch is immutable (true/false) + * --contract: PostageStamp contract address + * + * This task: + * - Estimates gas for the copyBatch transaction + * - Adds 20% buffer to estimated gas + * - Executes copyBatch with optimized gas settings + */ + import { task } from 'hardhat/config'; interface TaskArguments { diff --git a/tasks/deployments.ts b/tasks/deployments.ts index 6fce50dc..8747cb34 100644 --- a/tasks/deployments.ts +++ b/tasks/deployments.ts @@ -1,3 +1,16 @@ +/** + * Deployments Task - Display deployed contracts in copy-paste friendly format + * + * Usage: + * npx hardhat deployments + * + * This task displays: + * - All deployed contracts for both mainnet and testnet + * - Contract addresses with Etherscan explorer links + * - Clean, copy-paste friendly format + * - Automatically reads from mainnet_deployed.json and testnet_deployed.json + */ + import { task } from 'hardhat/config'; import fs from 'fs'; import path from 'path'; diff --git a/tasks/signatures.ts b/tasks/signatures.ts index 6ebcd51f..3b95a291 100644 --- a/tasks/signatures.ts +++ b/tasks/signatures.ts @@ -1,3 +1,21 @@ +/** + * Signatures Task - Generate ABI signatures for errors and functions + * + * Usage: + * npx hardhat sigs --c PostageStamp # Generate signatures for PostageStamp contract + * npx hardhat sigs --c PostageStamp --f MyFile # Use custom Solidity file name + * + * Parameters: + * --c: Contract name (required) + * --f: Solidity file name (optional, defaults to contract name) + * + * This task: + * - Loads contract ABI from artifacts + * - Generates error signatures with selectors + * - Shows function selectors for debugging + * - Useful for error handling and contract interaction + */ + import { task } from 'hardhat/config'; interface TaskArgs { diff --git a/tasks/status.ts b/tasks/status.ts index b3bb1e4c..30ce72e7 100644 --- a/tasks/status.ts +++ b/tasks/status.ts @@ -1,3 +1,18 @@ +/** + * Status Task - Check deployed contract statuses and roles + * + * Usage: + * npx hardhat status # Check mainnet (default) + * npx hardhat status --target mainnet # Check mainnet explicitly + * npx hardhat status --target testnet # Check testnet + * + * This task checks: + * - Contract pause/active status + * - Admin role assignments for specified addresses + * - Role verifications (PRICE_ORACLE_ROLE, REDISTRIBUTOR_ROLE, PAUSER_ROLE, etc.) + * - Displays results with visual indicators (✅/❌/🟢/🔴) + */ + import { task } from 'hardhat/config'; import { ethers } from 'ethers'; import * as fs from 'fs'; @@ -351,7 +366,7 @@ function displayStatus(result: StatusResult) { } task('status', 'Check status of deployed contracts and their roles') - .addParam('target', 'Network to check (testnet or mainnet)', 'testnet') + .addParam('target', 'Network to check (testnet or mainnet)', 'mainnet') .setAction(async (taskArgs) => { const { target } = taskArgs; From 01f66c5451938c2d12042aee90368741559e3774 Mon Sep 17 00:00:00 2001 From: Cardinal Date: Mon, 27 Oct 2025 12:51:52 +0100 Subject: [PATCH 2/3] info about all tasks --- tasks/README.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 146 insertions(+), 6 deletions(-) diff --git a/tasks/README.md b/tasks/README.md index 3995101d..01227993 100644 --- a/tasks/README.md +++ b/tasks/README.md @@ -73,11 +73,151 @@ The task includes robust error handling for: - Network connectivity issues - Missing deployment files -### Available Tasks +### Contracts Task + +The `contracts` task displays contract deployment information for a specific network. + +#### Usage + +```bash +# Show mainnet contracts +npx hardhat contracts --target main + +# Show testnet contracts +npx hardhat contracts --target test + +# Show local contracts +npx hardhat contracts --target local + +# Show pretestnet contracts +npx hardhat contracts --target pretestnet + +# Show tenderly contracts +npx hardhat contracts --target tenderly +``` + +#### What it displays + +- Contract addresses for the specified network +- Explorer URLs for each contract +- Formatted output for easy copying + +--- + +### Deployments Task + +The `deployments` task displays all deployed contracts in a copy-paste friendly format. + +#### Usage + +```bash +npx hardhat deployments +``` + +#### What it displays + +- All deployed contracts for both mainnet and testnet +- Contract addresses with Etherscan explorer links +- Clean, copy-paste friendly format +- Automatically reads from `mainnet_deployed.json` and `testnet_deployed.json` + +--- + +### Compare Task + +The `compare` task compares bytecodes between two deployments to verify consistency. + +#### Usage + +```bash +# Compare mainnet vs testnet +npx hardhat compare --source mainnet --target testnet + +# Compare testnet vs local +npx hardhat compare --source testnet --target local + +# Compare mainnet vs tenderly +npx hardhat compare --source mainnet --target tenderly +``` + +#### What it does + +- Compares bytecodes between deployments on different networks +- Shows which contracts are identical vs different +- Identifies missing contracts in either deployment +- Useful for verifying consistent deployments across networks +- Color-coded output: ✅ identical, ❌ different, ⚠️ missing + +--- + +### Signatures Task + +The `sigs` task generates ABI signatures for errors and functions. + +#### Usage + +```bash +# Generate signatures for PostageStamp contract +npx hardhat sigs --c PostageStamp + +# Use custom Solidity file name +npx hardhat sigs --c PostageStamp --f MyFile +``` + +#### Parameters + +- `--c`: Contract name (required) +- `--f`: Solidity file name (optional, defaults to contract name) + +#### What it does + +- Loads contract ABI from artifacts +- Generates error signatures with selectors +- Shows function selectors for debugging +- Useful for error handling and contract interaction + +--- + +### Copy Task + +The `copy` task uses the `copyBatch` function from the PostageStamp contract. + +#### Usage + +```bash +npx hardhat copy \ + --owner 0x1234... \ + --initialbalance 1000000000000000000 \ + --depth 20 \ + --bucketdepth 16 \ + --batchid 0xabcd... \ + --immutable false \ + --contract 0x5678... +``` + +#### Parameters + +- `--owner`: The account's address +- `--initialbalance`: Initial balance for the batch +- `--depth`: Batch depth +- `--bucketdepth`: Bucket depth +- `--batchid`: Batch ID +- `--immutable`: Whether batch is immutable (true/false) +- `--contract`: PostageStamp contract address + +#### What it does + +- Estimates gas for the copyBatch transaction +- Adds 20% buffer to estimated gas +- Executes copyBatch with optimized gas settings + +--- + +## Available Tasks Summary - `status`: Check contract statuses and roles -- `contracts`: Other contract-related tasks -- `deployments`: Deployment management tasks -- `compare`: Contract comparison utilities -- `signatures`: Signature verification tasks -- `copybatch`: Batch copying utilities \ No newline at end of file +- `contracts`: Display contract deployment information +- `deployments`: Show all deployed contracts in copy-paste friendly format +- `compare`: Compare bytecodes between deployments +- `signatures`: Generate ABI signatures for errors and functions +- `copy`: Use copyBatch function from PostageStamp contract \ No newline at end of file From a0fe821b5b05d335c6af02b8ac4e635800959dd0 Mon Sep 17 00:00:00 2001 From: Cardinal Date: Mon, 27 Oct 2025 13:07:30 +0100 Subject: [PATCH 3/3] add contracts and deployment docs --- README.md | 10 + docs/DEPLOYMENT.md | 509 +++++++++++++++++++++++++++++++++++++ docs/OVERVIEW.md | 249 ++++++++++++++++++ docs/POSTAGE_STAMP.md | 325 ++++++++++++++++++++++++ docs/PRICE_ORACLE.md | 325 ++++++++++++++++++++++++ docs/README.md | 75 ++++++ docs/REDISTRIBUTION.md | 556 +++++++++++++++++++++++++++++++++++++++++ docs/STAKING.md | 427 +++++++++++++++++++++++++++++++ 8 files changed, 2476 insertions(+) create mode 100644 docs/DEPLOYMENT.md create mode 100644 docs/OVERVIEW.md create mode 100644 docs/POSTAGE_STAMP.md create mode 100644 docs/PRICE_ORACLE.md create mode 100644 docs/README.md create mode 100644 docs/REDISTRIBUTION.md create mode 100644 docs/STAKING.md diff --git a/README.md b/README.md index adcda8ac..64e79959 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ This repository contains the smart contracts for Swarm's storage incentives. +## 📚 Documentation + +Comprehensive documentation is available in the [`docs/`](docs/) directory: +- [Overview](./docs/OVERVIEW.md) - System architecture and mechanics +- [PostageStamp](./docs/POSTAGE_STAMP.md) - Postage stamp batch management +- [PriceOracle](./docs/PRICE_ORACLE.md) - Dynamic pricing system +- [StakeRegistry](./docs/STAKING.md) - Staking for node operators +- [Redistribution](./docs/REDISTRIBUTION.md) - Schelling game details +- [Deployment Guide](./docs/DEPLOYMENT.md) - How to deploy contracts + # Overview In order to distribute to upload content to the Swarm network, _batches_ of _postage stamps_ are purchased by nodes. These _stamps_ are then attached to content that is divided into 4kb chunks and then uploaded to the Swarm network. In order to distribute the proceeds from the sales of these _batches_, a [Schelling Co-ordination Game](https://en.wikipedia.org/wiki/Coordination_game) is implemented using the smart contracts contained in this repository, in order to identify nodes storing the canonical subset of valid chunks that fall within the radius of responsibility of each node in a _neighbourhood_ at the time of their application. Correct identification of this hash qualifies a node to apply to receive a reward comprising value arising from _expired_ _batches_. diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 00000000..da43b93e --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,509 @@ +# Deployment Guide + +## Overview + +This guide covers how to deploy and configure the Swarm Storage Incentive contracts on various networks. + +## Prerequisites + +- Hardhat development environment +- Node.js and yarn/npm +- Access to network (mainnet requires real ETH) + +## Deployment Order + +The contracts must be deployed in this specific order due to dependencies: + +``` +1. Token (external or TestToken for testnets) +2. PostageStamp (depends on Token) +3. PriceOracle (depends on PostageStamp) +4. StakeRegistry (depends on Token and PriceOracle) +5. Redistribution (depends on StakeRegistry, PostageStamp, PriceOracle) +6. Role Setup (connects contracts together) +``` + +## Network Configuration + +### Mainnet + +- **Chain ID**: 1 +- **Swarm Network ID**: 1 +- **Block Confirmations**: 6 +- **Token**: Must use deployed BZZ token +- **Multisig**: `0xD5C070FEb5EA883063c183eDFF10BA6836cf9816` + +### Testnet (Sepolia) + +- **Chain ID**: 11155111 +- **Swarm Network ID**: 10 +- **Block Confirmations**: 6 +- **Token**: Uses TestToken with minting +- **Multisig**: `0xb1C7F17Ed88189Abf269Bf68A3B2Ed83C5276aAe` + +### Testnet Light + +- **Chain ID**: TBD +- **Swarm Network ID**: 5 +- **Block Confirmations**: 6 +- **Token**: Uses TestToken +- **Multisig**: `0xb1C7F17Ed88189Abf269Bf68A3B2Ed83C5276aAe` + +### Tenderly + +- **Swarm Network ID**: 1 +- **Block Confirmations**: 1 +- **Used for**: Testing deployments + +## Deployment Scripts + +### Step 1: Token + +**File**: `deploy/main/000_deploy_token.ts` + +For mainnet: +```bash +# Expects Token to already exist +# Will error if not found +``` + +For testnets: +```bash +# Deploys TestToken with 16 decimals +# Mints initial supply to deployer +``` + +### Step 2: PostageStamp + +**File**: `deploy/main/001_deploy_postage.ts` + +**Constructor**: +```typescript +[token.address, 16] // minimumBucketDepth = 16 +``` + +**Deployment**: +```bash +npx hardhat deploy --network mainnet --tags postageStamp +``` + +### Step 3: PriceOracle + +**File**: `deploy/main/002_deploy_oracle.ts` + +**Constructor**: +```typescript +[postageStamp.address] +``` + +**Special Handling**: +- If oracle exists, preserves old price +- Re-applies old price after redeployment + +**Deployment**: +```bash +npx hardhat deploy --network mainnet --tags oracle +``` + +### Step 4: StakeRegistry + +**File**: `deploy/main/003_deploy_staking.ts` + +**Constructor**: +```typescript +[token.address, swarmNetworkId, priceOracle.address] +``` + +**Network IDs** (from `helper-hardhat-config.ts`): +- Mainnet: 1 +- Testnet: 10 +- Testnet Light: 5 +- Tenderly: 1 + +**Deployment**: +```bash +npx hardhat deploy --network mainnet --tags staking +``` + +### Step 5: Redistribution + +**File**: `deploy/main/004_deploy_redistribution.ts` + +**Constructor**: +```typescript +[stakeRegistry.address, postageStamp.address, priceOracle.address] +``` + +**Deployment**: +```bash +npx hardhat deploy --network mainnet --tags redistribution +``` + +### Step 6: Role Configuration + +#### PostageStamp Roles + +**File**: `deploy/main/005_deploy_roles_postage.ts` + +**Grants**: +- `PRICE_ORACLE_ROLE` → PriceOracle contract +- `REDISTRIBUTOR_ROLE` → Redistribution contract + +#### Redistribution Roles + +**File**: `deploy/main/006_deploy_roles_redistribution.ts` + +Currently no roles need to be set (constructor handles it). + +#### StakeRegistry Roles + +**File**: `deploy/main/007_deploy_roles_staking.ts` + +**Grants**: +- `REDISTRIBUTOR_ROLE` → Redistribution contract + +#### PriceOracle Roles + +**File**: `deploy/main/008_deploy_roles_oracle.ts` + +**Grants**: +- `PRICE_UPDATER_ROLE` → Redistribution contract + +**Deployment**: +```bash +npx hardhat deploy --network mainnet --tags roles +``` + +## Full Deployment + +Deploy all contracts in order: + +```bash +# Deploy all contracts +npx hardhat deploy --network mainnet + +# Deploy only contracts (no roles) +npx hardhat deploy --network mainnet --tags contracts + +# Deploy only roles +npx hardhat deploy --network mainnet --tags roles +``` + +## Deployment Status + +### Mainnet + +**Network**: Ethereum Mainnet +**Chain ID**: 1 +**Token**: 0x... (BZZ token) +**PostageStamp**: 0x... +**PriceOracle**: 0x... +**StakeRegistry**: 0x... +**Redistribution**: 0x... + +See `mainnet_deployed.json` for current addresses. + +### Testnet (Sepolia) + +**Network**: Sepolia Testnet +**Chain ID**: 11155111 +See `testnet_deployed.json` for current addresses. + +## Verification + +After deployment, verify contracts using Hardhat: + +```bash +npx hardhat verify --network mainnet \ + \ + ... +``` + +## Network Configuration + +Configuration is stored in `helper-hardhat-config.ts`: + +```typescript +export const networkConfig: networkConfigInfo = { + mainnet: { + blockConfirmations: 6, + swarmNetworkId: 1, + multisig: '0xD5C070FEb5EA883063c183eDFF10BA6836cf9816', + }, + testnet: { + blockConfirmations: 6, + swarmNetworkId: 10, + multisig: '0xb1C7F17Ed88189Abf269Bf68A3B2Ed83C5276aAe', + }, + // ... other networks +}; +``` + +## Role Management + +### Granting Roles + +```bash +# Example: Grant PRICE_ORACLE_ROLE to an address +npx hardhat send-tx --network mainnet \ + --contract PostageStamp \ + --method grantRole \ + --args ROLE_HASH ADDRESS +``` + +### Renouncing Roles (Making Immutable) + +```bash +# Renounce admin roles to make contracts immutable +npx hardhat send-tx --network mainnet \ + --contract PostageStamp \ + --method renounceRole \ + --args ROLE_HASH ADDRESS +``` + +## Initial Setup + +### 1. Set Initial Price + +After oracle deployment: + +```bash +npx hardhat send-tx --network mainnet \ + --contract PriceOracle \ + --method setPrice \ + --args 24000 +``` + +This sets the initial price to 24000 (downscaled). + +### 2. Configure Minimum Validity + +Set minimum batch validity (24h default): + +```bash +npx hardhat send-tx --network mainnet \ + --contract PostageStamp \ + --method setMinimumValidityBlocks \ + --args 17280 # 24 * 60 * 60 / 5 +``` + +### 3. Batch Migration + +If migrating from old contract: + +```typescript +const oldBatches = await getOldBatches(); +await PostageStamp.copyBatchBulk(oldBatches); +``` + +## Monitoring + +Check contract status: + +```bash +npx hardhat status --target mainnet +``` + +This shows: +- Contract pause status +- Admin roles +- Role assignments +- Connection status + +## Troubleshooting + +### Deployment Fails: Token Not Found + +**Error**: "Token not available" + +**Solution**: For mainnet, token must be deployed first. For testnets, ensure TestToken deployment runs. + +### Role Assignment Fails + +**Error**: "Deployer needs to have admin role" + +**Solution**: Grant admin role to deployer or execute transactions manually from admin account. + +### Out of Gas + +**Error**: Transaction reverted + +**Solution**: Use `copyBatchBulk()` for batch migrations (processes 60-90 batches per tx). + +### Price Update Failed + +**Warning**: `StampPriceUpdateFailed` event + +**Cause**: PostageStamp not responding to price update +**Solution**: Check PostageStamp status, ensure not paused + +## Upgrade Path + +Since contracts are NOT upgradeable, upgrades require: + +1. Deploy new contracts +2. Use `copyBatch()` to migrate batches (admin only) +3. Allow stake migration via `migrateStake()` when paused +4. Transfer admin roles to multisig +5. Renounce old admin/pauser roles + +## Security Checklist + +- [ ] All admin roles granted to multisig +- [ ] Pauser roles granted to multisig +- [ ] Initial price set on oracle +- [ ] Minimum validity configured +- [ ] All contracts verified on Etherscan +- [ ] Role setup verified +- [ ] Test initial deposit and withdrawal +- [ ] Test pause/unpause functionality + +## Production Deployment + +### Mainnet Deployment Steps + +1. **Deploy Token** (if not exists) + ```bash + npx hardhat deploy --network mainnet --tags token + ``` + +2. **Deploy Contracts** + ```bash + npx hardhat deploy --network mainnet --tags contracts + ``` + +3. **Setup Roles** + ```bash + npx hardhat deploy --network mainnet --tags roles + ``` + +4. **Set Initial Price** + ```bash + npx hardhat send-tx --network mainnet \ + --contract PriceOracle \ + --method setPrice \ + --args 24000 + ``` + +5. **Grant Additional Admins** (e.g., multisig) + ```bash + npx hardhat grant-role --network mainnet \ + --contract PostageStamp \ + --role DEFAULT_ADMIN_ROLE \ + --to 0xD5C070FEb5EA883063c183eDFF10BA6836cf9816 + ``` + +6. **Verify Contracts** + ```bash + npx hardhat verify --network mainnet --all + ``` + +7. **Check Status** + ```bash + npx hardhat status --target mainnet + ``` + +8. **Test End-to-End** + - Create test batch + - Stake tokens + - Participate in redistribution game + +## Configuration Files + +### hardhat.config.ts + +Configures: +- Compiler version (Sol ≥ 0.8.19) +- Networks (mainnet, testnet, etc.) +- Etherscan verification +- Gas optimization + +### helper-hardhat-config.ts + +Defines: +- Network IDs +- Block confirmations +- Multisig addresses +- Swarm network IDs + +### deployments/ + +Stores: +- Deployment artifacts +- Addresses and ABIs +- Constructor arguments +- Verification data + +## Common Commands + +```bash +# Deploy all +npx hardhat deploy --network mainnet + +# Deploy specific tag +npx hardhat deploy --network mainnet --tags postageStamp + +# Run scripts +npx hardhat run scripts/cluster/changePrice.ts --network mainnet + +# Check status +npx hardhat status --target mainnet + +# Verify contracts +npx hardhat verify --network mainnet CONTRACT_ADDRESS ... + +# Get contract info +npx hardhat contracts --target main +``` + +## Testing Deployment + +### Local Testing + +```bash +# Deploy to local hardhat network +npx hardhat deploy --network localhost + +# Run tests +npx hardhat test +``` + +### Testnet Testing + +```bash +# Deploy to testnet +npx hardhat deploy --network testnet + +# Verify on Etherscan +npx hardhat verify --network testnet --all +``` + +### Gas Estimation + +```bash +# Estimate deployment gas +npx hardhat deploy --network mainnet --dry-run +``` + +## Monitoring After Deployment + +Use the status task to monitor contract health: + +```bash +npx hardhat status --target mainnet +``` + +Monitor for: +- Contract pause status +- Admin role assignments +- Role configuration +- Connection issues + +## Support + +For issues or questions: +- Check deployment logs in `deployments/` directory +- Review error messages in transaction receipts +- Check contract status using `status` task +- Verify role setup using events + diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md new file mode 100644 index 00000000..1c2f973e --- /dev/null +++ b/docs/OVERVIEW.md @@ -0,0 +1,249 @@ +# Swarm Storage Incentive System - Overview + +## Introduction + +The Swarm Storage Incentive (SI) system is a decentralized mechanism that rewards nodes for storing data on the Swarm network. The system consists of four main smart contracts that work together to manage postage stamps, dynamic pricing, staking, and redistribution rewards. + +## System Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Smart Contracts System │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Token │───▶│ PostageStamp │◀───│ PriceOracle │ │ +│ │ (ERC20) │ │ │ │ │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ │ │ │ │ +│ ▼ ▼ │ │ +│ ┌──────────────┐ ┌──────────────┐ │ │ +│ │ Staking │◀───│ Redistribution│──────┘ │ +│ │ Registry │ │ │ │ +│ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## System Flow + +### 1. Postage Stamp Creation + +Users purchase postage stamps to store data on Swarm: + +``` +User → PostageStamp.createBatch() + ├─ Calculate initial balance per chunk + ├─ Normalize balance with current price + ├─ Store batch in order statistics tree + └─ Track valid chunk capacity +``` + +**Key Concept**: Normalized balance represents the balance as if the batch existed since contract inception, accounting for all historical price changes. + +### 2. Staking for Node Operators + +Node operators stake tokens to participate in the redistribution game: + +``` +Node Operator → StakeRegistry.manageStake() + ├─ Calculate overlay from network ID + nonce + ├─ Calculate committed stake (in storage units) + ├─ Track potential stake (in BZZ tokens) + └─ Allow withdrawal of surplus stake +``` + +**Key Concept**: Height parameter allows nodes to register additional storage capacity (2^height multiplier). + +### 3. Redistribution Game Phases + +The redistribution game runs in continuous rounds with three distinct phases: + +#### Phase 1: Commit (25% of round = 38 blocks ≈ 3 minutes) +- Nodes calculate reserve commitment hash from stored chunks +- Create obfuscated commit with random nonce +- Submit hash commitment + +#### Phase 2: Reveal (25% of round = 38 blocks ≈ 3 minutes) +- Nodes reveal the actual values used to create commit +- Randomness is updated after each reveal +- Anchor is set for proximity calculations +- Only revealed commits in proximity are valid + +#### Phase 3: Claim (50% of round = 76 blocks ≈ 6 minutes) +- Select truth-teller based on stake density +- Randomly select winner from truth-tellers +- Verify proof of chunk inclusion in reserve +- Transfer pot from PostageStamp to winner + +### 4. Price Adjustment + +``` +Redistribution → PriceOracle.adjustPrice(redundancy) + ├─ Calculate redundancy count from reveals + ├─ Adjust price based on target vs actual + ├─ Update PostageStamp price + └─ Pot accumulates from expired batches +``` + +**Key Mechanism**: Price increases when redundancy < target, decreases when redundancy > target. + +## Economic Model + +### Postage Stamp Economics + +- **Initial Balance**: Paid upfront when creating batch +- **Normalized Balance**: Per-chunk storage cost accumulated over time +- **Valid Chunk Count**: Current capacity of valid batches +- **Pot**: Accumulated funds from expired batches + +**Expiration Formula**: +``` +Batch expires when: remainingBalance(batchId) <= 0 +remainingBalance = normalisedBalance - currentTotalOutPayment() +``` + +### Staking Economics + +- **Committed Stake**: Amount of chunks pledged to store (in oracle price units) +- **Potential Stake**: Actual BZZ tokens staked +- **Effective Stake**: `min(committed_stake * price * 2^height, potential_stake)` + +**Example**: +- Node stakes 1000 BZZ at price 1000 chunks/BZZ with height 2 +- Committed stake: 100 chunks +- Effective stake: min(100 * 1000 * 4, 1000 BZZ) = 1000 BZZ + +### Redistribution Economics + +- **Round Length**: 152 blocks (~12.7 minutes at 5s/block) +- **Pot Source**: Expired batches accumulate funds +- **Winner Selection**: Stochastic selection weighted by stake density +- **Truth Selection**: Median reveal (by stake density) for consensus + +## Security Model + +### Role-Based Access Control + +Each contract defines specific roles: + +**PostageStamp**: +- `DEFAULT_ADMIN_ROLE`: Full admin control +- `PRICE_ORACLE_ROLE`: Can update prices (granted to PriceOracle) +- `REDISTRIBUTOR_ROLE`: Can withdraw pot (granted to Redistribution) +- `PAUSER_ROLE`: Can pause/unpause contract + +**PriceOracle**: +- `DEFAULT_ADMIN_ROLE`: Can manually set price, pause +- `PRICE_UPDATER_ROLE`: Can adjust price based on redundancy (granted to Redistribution) + +**StakeRegistry**: +- `DEFAULT_ADMIN_ROLE`: Change network ID, pause +- `REDISTRIBUTOR_ROLE`: Freeze and slash deposits (granted to Redistribution) + +**Redistribution**: +- `DEFAULT_ADMIN_ROLE`: Adjust freezing parameters, pause + +### Penalties + +Nodes that behave dishonestly face penalties: + +1. **Non-Reveal Penalty**: 2x rounds frozen if committed but didn't reveal +2. **Disagreement Penalty**: 1x rounds frozen (with random factor) if revealed wrong truth +3. **Depth-Based Penalty**: Freeze duration = base * 2^reported_depth + +### Pausability + +All contracts (except Token) implement pausable functionality for emergency situations. They can be provably stopped by renouncing the pauser and admin roles after pausing. + +## Key Algorithms + +### Batch Expiration Algorithm + +```solidity +function expireLimited(uint256 limit) { + for (each batch in ascending balance order) { + if (batch.balance <= currentTotalOutPayment) { + // Batch expired + pot += batchSize * (balance - lastExpiryBalance) + validChunkCount -= batchSize + delete batch + } else { + break + } + } + pot += validChunkCount * (currentTotalOutPayment - lastExpiryBalance) +} +``` + +### Truth Selection Algorithm + +```solidity +function getCurrentTruth() { + currentSum = 0 + for (each reveal in commit order) { + currentSum += reveal.stakeDensity + if (random < (reveal.stakeDensity / currentSum)) { + truth = reveal + } + } + return truth.hash, truth.depth +} +``` + +### Winner Selection Algorithm + +```solidity +function winnerSelection() { + truth = getCurrentTruth() + currentSum = 0 + for (each reveal matching truth) { + currentSum += reveal.stakeDensity + if (random < (reveal.stakeDensity / currentSum)) { + winner = reveal + } + } +} +``` + +## Gas Optimization + +### Batch Import + +For large migrations, use `copyBatchBulk()` which can process 60-90 batches per transaction, with automatic error handling for failed imports. + +### Limited Expiration + +The `expireLimited()` function allows capping gas usage when many batches expire, preventing block gas limit issues. + +### State Variables + +- Uses `unchecked` blocks for safe arithmetic +- Leverages order statistics tree for O(log n) batch management +- Minimal storage writes in hot paths + +## Upgrade Path + +Currently, contracts are NOT upgradeable. For major updates: +1. Deploy new contracts +2. Migrate batches using `copyBatch()` (admin only) +3. Allow users to migrate stakes when paused +4. Transfer admin roles to multisig + +## Network IDs + +Swarm uses different network IDs for different deployments: +- Mainnet: ID 1 +- Testnet (Sepolia): ID 10 +- Testnet Light: ID 5 + +This ensures network isolation and prevents overlay conflicts. + +## Further Reading + +- [PostageStamp Details](./POSTAGE_STAMP.md) +- [PriceOracle Details](./PRICE_ORACLE.md) +- [StakeRegistry Details](./STAKING.md) +- [Redistribution Details](./REDISTRIBUTION.md) +- [Deployment Guide](./DEPLOYMENT.md) + diff --git a/docs/POSTAGE_STAMP.md b/docs/POSTAGE_STAMP.md new file mode 100644 index 00000000..d832dea0 --- /dev/null +++ b/docs/POSTAGE_STAMP.md @@ -0,0 +1,325 @@ +# PostageStamp Contract + +## Overview + +The `PostageStamp` contract manages postage stamp batches that users purchase to store chunks on the Swarm network. It implements a sophisticated price normalization system that tracks storage costs over time. + +## Purpose + +Users buy postage stamps (batches) upfront to pay for future data storage. The contract: +- Tracks batches with their storage capacity and balance +- Manages batch expiration based on price accumulation +- Accumulates expired batch funds into a pot for redistribution +- Provides role-based access for price updates and withdrawals + +## Key Concepts + +### Normalized Balance + +The contract uses a "normalized balance" system to track the actual storage cost accumulated over time: + +```solidity +normalizedBalance = totalOutPayment + initialBalancePerChunk +``` + +- `totalOutPayment`: Accumulated per-chunk cost since contract deployment +- New batches are credited with current `totalOutPayment` as if they existed since inception +- When price changes, `totalOutPayment` is updated based on blocks elapsed + +### Batch Structure + +```solidity +struct Batch { + address owner; // Owner of the batch + uint8 depth; // Total depth (2^depth = max chunks) + uint8 bucketDepth; // Bucket depth for addressing + bool immutableFlag; // Whether batch can be modified + uint256 normalisedBalance; // Normalized balance per chunk + uint256 lastUpdatedBlockNumber; // Last update timestamp +} +``` + +### Order Statistics Tree + +Batches are stored in an ordered tree structure sorted by normalized balance. This enables: +- Efficient expiration checking (start from lowest balance) +- O(log n) operations for insert/remove +- Predictable gas costs for batch lookups + +## Functions + +### User Functions + +#### createBatch() +Creates a new postage stamp batch. + +**Parameters**: +- `_owner`: Address that will own the batch +- `_initialBalancePerChunk`: Balance to add per chunk +- `_depth`: Total batch depth (capacity = 2^depth) +- `_bucketDepth`: Bucket depth for chunk addressing +- `_nonce`: Random nonce for batch ID generation +- `_immutable`: Whether batch can be topped up later + +**Requirements**: +- `_initialBalancePerChunk >= minimumInitialBalancePerChunk()` (24h minimum validity) +- `_bucketDepth >= minimumBucketDepth && _bucketDepth < _depth` +- Sufficient ERC20 token approval + +**Returns**: `bytes32 batchId` + +**Batch ID Generation**: +```solidity +batchId = keccak256(abi.encode(msg.sender, _nonce)) +``` + +#### topUp() +Adds more balance to an existing batch. + +**Parameters**: +- `_batchId`: ID of the batch to top up +- `_topupAmountPerChunk`: Additional balance per chunk + +**Requirements**: +- Batch must exist and not be expired +- Batch depth must be > minimumBucketDepth +- New total balance must meet minimum validity + +**Effects**: +- Transfers tokens from caller +- Updates normalized balance +- Reinserts batch into tree with new balance + +#### increaseDepth() +Increases the depth (capacity) of a batch. + +**Parameters**: +- `_batchId`: ID of the batch +- `_newDepth`: New depth value (must be larger than current) + +**Requirements**: +- Caller must be batch owner +- `_newDepth > batch.depth` +- Batch must not be expired +- New balance per chunk must meet minimum validity + +**Effects**: +- Doubles capacity for each additional depth level +- Redistributes existing balance across new capacity + +### Admin Functions + +#### copyBatch() +Manually creates a batch (for migrations). + +**Parameters**: Same as `createBatch()`, plus `_batchId` (the specific ID to use) + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call +- Used during contract migrations to preserve batch data + +#### copyBatchBulk() +Bulk import batches (for large migrations). + +**Parameters**: +- `bulkBatches`: Array of ImportBatch structures + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call +- Processes 60-90 batches optimally +- Emits `CopyBatchFailed` event if batch import fails + +### Price Management + +#### setPrice() +Updates the price per chunk. + +**Parameters**: +- `_price`: New price value + +**Requirements**: +- Only `PRICE_ORACLE_ROLE` can call + +**Logic**: +```solidity +if (lastPrice != 0) { + // Account for price accumulation since last update + totalOutPayment = currentTotalOutPayment() +} +lastPrice = _price +lastUpdatedBlock = block.number +``` + +### Expiration Management + +#### expireLimited() +Reclaims expired batches (called automatically or manually). + +**Parameters**: +- `limit`: Maximum number of batches to expire (prevents gas limit issues) + +**Logic**: +1. Iterate batches in ascending balance order +2. If `remainingBalance(batch) <= 0`: + - Remove chunks from `validChunkCount` + - Add to pot: `pot += batchSize * (normalizedBalance - lastExpiryBalance)` + - Delete batch +3. For remaining valid batches: + - `pot += validChunkCount * (currentTotalOutPayment - lastExpiryBalance)` +4. Update `lastExpiryBalance` + +### Pot Withdrawal + +#### withdraw() +Withdraws the accumulated pot to a beneficiary. + +**Parameters**: +- `beneficiary`: Address to receive the funds + +**Requirements**: +- Only `REDISTRIBUTOR_ROLE` can call + +**Returns**: Transfers current pot amount and resets it to 0 + +### View Functions + +#### remainingBalance(batchId) +Returns the unused balance per chunk for a batch. + +#### currentTotalOutPayment() +Returns the total per-chunk cost since contract deployment. + +#### totalPot() +Returns the current pot amount (also calls `expireLimited`). + +#### validChunkCount +Public variable representing total chunks available from all active batches. + +#### minimumInitialBalancePerChunk() +Returns minimum balance for 24h validity: `minimumValidityBlocks * lastPrice` + +## Events + +```solidity +event BatchCreated( + bytes32 indexed batchId, + uint256 totalAmount, + uint256 normalisedBalance, + address owner, + uint8 depth, + uint8 bucketDepth, + bool immutableFlag +); + +event BatchTopUp( + bytes32 indexed batchId, + uint256 topupAmount, + uint256 normalisedBalance +); + +event BatchDepthIncrease( + bytes32 indexed batchId, + uint8 newDepth, + uint256 normalisedBalance +); + +event PriceUpdate(uint256 price); +event PotWithdrawn(address recipient, uint256 totalAmount); +``` + +## Roles + +- **DEFAULT_ADMIN_ROLE**: Full admin access, can grant/revoke other roles +- **PRICE_ORACLE_ROLE**: Can update prices (typically PriceOracle contract) +- **REDISTRIBUTOR_ROLE**: Can withdraw pot (typically Redistribution contract) +- **PAUSER_ROLE**: Can pause/unpause the contract + +## Deployment Configuration + +```typescript +constructor(address _bzzToken, uint8 _minimumBucketDepth) +``` + +- `_bzzToken`: ERC20 token address for payments +- `_minimumBucketDepth`: Minimum bucket depth (typically 16) + +## Pausability + +The contract implements `Pausable` from OpenZeppelin: +- Pauses all user operations (createBatch, topUp, increaseDepth) +- Admin operations (setPrice, copyBatch) can still proceed +- Can be made immutable by renouncing Pauser and Admin roles + +## Gas Considerations + +- Batch expiration is bounded (`expireLimited()`) to prevent gas limit issues +- Tree operations are O(log n) +- Bulk imports optimize gas usage (60-90 batches per transaction) + +## Examples + +### Creating a Batch + +```solidity +// User approves tokens +ERC20(bzzToken).approve(postageStamp, amount); + +// Create batch +bytes32 batchId = PostageStamp(postageStamp).createBatch( + owner, + 1000000000000000, // 0.001 tokens per chunk + 20, // depth = 2^20 = 1,048,576 chunks + 16, // bucketDepth + keccak256("nonce"), // unique nonce + false // mutable +); +``` + +### Topping Up a Batch + +```solidity +PostageStamp(postageStamp).topUp( + batchId, + 500000000000000 // add 0.0005 tokens per chunk +); +``` + +### Checking Batch Status + +```solidity +uint256 remaining = PostageStamp(postageStamp).remainingBalance(batchId); +if (remaining > 0) { + // Batch is still valid +} +``` + +## Related Contracts + +- **PriceOracle**: Sets price via PRICE_ORACLE_ROLE +- **Redistribution**: Withdraws pot via REDISTRIBUTOR_ROLE +- **Token**: ERC20 token used for payments + +## Security Considerations + +1. Batch IDs are derived from transaction sender and nonce to prevent collisions +2. Minimum balance enforces 24h minimum batch validity +3. Normalized balance system prevents price manipulation attacks +4. Expiration process is atomic and gas-bounded +5. Admin functions protected by role-based access control + +## Error Codes + +```solidity +error ZeroAddress(); // Owner cannot be zero +error InvalidDepth(); // Invalid depth parameters +error BatchExists(); // Batch ID already exists +error InsufficientBalance(); // Below minimum balance requirement +error BatchExpired(); // Batch has expired +error BatchTooSmall(); // Depth too small for top-up +error NotBatchOwner(); // Caller is not batch owner +error PriceOracleOnly(); // Only price oracle can set price +error InsufficienChunkCount(); // Invalid chunk count +error OnlyRedistributor(); // Only redistributor can withdraw +error OnlyPauser(); // Only pauser can pause/unpause +``` + diff --git a/docs/PRICE_ORACLE.md b/docs/PRICE_ORACLE.md new file mode 100644 index 00000000..8fa8a6ca --- /dev/null +++ b/docs/PRICE_ORACLE.md @@ -0,0 +1,325 @@ +# PriceOracle Contract + +## Overview + +The `PriceOracle` contract implements a dynamic pricing mechanism for storage on the Swarm network. It automatically adjusts prices based on network redundancy to maintain optimal storage coverage. + +## Purpose + +The oracle: +- Dynamically adjusts per-chunk storage prices based on actual network redundancy +- Targets a specific redundancy level (default: 4 copies per chunk) +- Updates prices every 152 blocks (~19 minutes on Ethereum) +- Enforces a minimum price floor +- Integrates with PostageStamp to keep prices synchronized + +## Key Concepts + +### Target Redundancy + +The system aims for a target redundancy of 4, meaning on average each chunk should be stored on 4 nodes. + +### Price Adjustment Mechanism + +Prices adjust based on revealed nodes in the redistribution game: +- **High redundancy** (> target): Price decreases (discourage storage) +- **Low redundancy** (< target): Price increases (encourage storage) +- **Target redundancy**: Price stays stable + +### Rounds + +The oracle operates in rounds of 152 blocks: +- Roughly 19 minutes on Ethereum (5s blocks) +- Only one price adjustment per round +- Skips rounds accumulate maximum price increase + +### Change Rate Table + +The contract uses a lookup table for price changes: + +```solidity +// [max decrease, ..., stable, ..., max increase] +changeRate = [ + 1049417, // 4+ extra redundancy → +0.08%/round + 1049206, // 3 extra redundancy → +0.06% + 1048996, // 2 extra redundancy → +0.04% + 1048786, // 1 extra redundancy → +0.02% + 1048576, // Target redundancy → 0% + 1048366, // 1 below target → -0.02% + 1048156, // 2 below target → -0.04% + 1047946, // 3 below target → -0.06% + 1047736 // 4 below target → -0.08% +] +``` + +The index in this array represents: `redundancy - targetRedundancy + 4` + +## Functions + +### Admin Functions + +#### setPrice() +Manually sets the price (for initialization or emergency). + +**Parameters**: +- `_price`: New price value + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call + +**Logic**: +```solidity +currentPriceUpScaled = _price << 10 // upscale by 2^10 +if (currentPriceUpScaled < minimumPriceUpscaled) { + currentPriceUpScaled = minimumPriceUpscaled +} +// Update PostageStamp +PostageStamp.setPrice(currentPrice()) +emit PriceUpdate(currentPrice()) +``` + +#### adjustPrice() +Automatically adjusts price based on redundancy (called by Redistribution). + +**Parameters**: +- `redundancy`: Number of nodes that revealed in the current round + +**Requirements**: +- Only `PRICE_UPDATER_ROLE` can call (typically Redistribution contract) +- Contract must not be paused +- Can only be called once per round + +**Logic**: +1. Check if already adjusted this round +2. Cap redundancy at `targetRedundancy + maxConsideredExtraRedundancy` +3. Apply change rate based on redundancy - target +4. Apply maximum penalty for skipped rounds +5. Enforce minimum price +6. Update PostageStamp +7. Emit event + +**Skipped Rounds Handling**: +```solidity +if (skippedRounds > 0) { + // Apply maximum increase rate for each skipped round + for each skipped round { + currentPriceUpScaled = (changeRate[0] * currentPriceUpScaled) / priceBase + } +} +``` + +#### pause() / unPause() +Pauses or unpauses price adjustments. + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call + +**Effects**: +- When paused: `adjustPrice()` returns false without doing anything +- Manual `setPrice()` still works + +### View Functions + +#### currentPrice() +Returns the current price (downscaled by 2^10). + +#### minimumPrice() +Returns the minimum price floor. + +#### currentRound() +Returns the current round number: `block.number / 152` + +### Configuration Parameters + +```solidity +uint16 targetRedundancy = 4; // Target chunks per node +uint16 maxConsideredExtraRedundancy = 4; // Cap on extra redundancy +uint32 minimumPriceUpscaled = 24000 << 10; // ~23.44 (downscaled) +uint32 priceBase = 1048576; // Base for change rate (2^20) +``` + +## Events + +```solidity +event PriceUpdate(uint256 price); // Emitted on price changes +event StampPriceUpdateFailed(uint256 attemptedPrice); // If PostageStamp update fails +``` + +## Roles + +- **DEFAULT_ADMIN_ROLE**: Can set price manually, pause/unpause +- **PRICE_UPDATER_ROLE**: Can call adjustPrice() (granted to Redistribution) + +## Deployment Configuration + +```typescript +constructor(address _postageStamp) +``` + +- `_postageStamp`: Address of PostageStamp contract + +**Initialization**: +- Sets up admin role +- Links to PostageStamp +- Sets `lastAdjustedRound = currentRound()` +- Emits initial price + +## Price Calculation Details + +### Upscaling + +Prices are stored upscaled by 2^10 (1024) to avoid rounding errors in integer arithmetic: +- **Stored**: `24000 << 10 = 24576000` +- **Displayed**: `24576000 >> 10 = 24000` +- Allows for fractional change rates without floating point + +### Change Rate Calculation + +The change rate is applied multiplicatively: +```solidity +newPrice = (changeRate * oldPrice) / priceBase +``` + +For example, with changeRate = 1049417: +``` +newPrice = (1049417 * 1000000) / 1048576 = 1000800 +// Increase of ~0.08% +``` + +### Minimum Price Enforcement + +Prices are bounded from below: +```solidity +if (currentPriceUpScaled < minimumPriceUpscaled) { + currentPriceUpScaled = minimumPriceUpscaled +} +``` + +This prevents prices from becoming too low and disincentivizing storage. + +## Integration with Other Contracts + +### PostageStamp Integration + +After every price change, the oracle updates PostageStamp: +```solidity +(bool success, ) = address(postageStamp).call( + abi.encodeWithSignature("setPrice(uint256)", uint256(currentPrice())) +); +``` + +If the update fails, an event is emitted but the adjustment continues. + +### Redistribution Integration + +Redistribution calls `adjustPrice(redundancyCount)` after each reveal phase, using the count of valid reveals as the redundancy metric. + +## Round Schedule + +``` +Round N: + Block 0 - Round starts (adjustPrice can be called) + Block 1-38 - Reveal phase (Redistribution collects reveals) + Block 38 - adjustPrice called with redundancy count + Block 152 - Round ends, new round starts +``` + +## Examples + +### Manual Price Update + +```solidity +// Admin manually sets price to 50000 +PriceOracle(oracle).setPrice(50000); +``` + +### Automatic Adjustment + +```solidity +// Redistribution calls after reveal phase +// If 6 nodes revealed and target is 4: +// redundancy - target = 6 - 4 = 2 → index 2 + 4 = 6 +// changeRate[6] = 1048996 → price increases by ~0.04% + +PriceOracle(oracle).adjustPrice(6); +``` + +### Checking Current Price + +```solidity +uint32 price = PriceOracle(oracle).currentPrice(); +uint32 min = PriceOracle(oracle).minimumPrice(); +uint64 round = PriceOracle(oracle).currentRound(); +``` + +## Error Codes + +```solidity +error CallerNotAdmin(); // Only admin can call +error CallerNotPriceUpdater(); // Only price updater can call +error PriceAlreadyAdjusted(); // Already adjusted this round +error UnexpectedZero(); // Redundancy must be > 0 +``` + +## Security Considerations + +1. Minimum price floor prevents race-to-bottom pricing +2. Maximum extra redundancy cap prevents excessive price increases +3. One adjustment per round prevents manipulation +4. Pausable for emergency stops +5. Failed PostageStamp updates don't prevent oracle updates + +## Pause Mechanism + +When paused: +- `adjustPrice()` returns `false` without making changes +- `setPrice()` still works for manual intervention +- Can be made immutable by renouncing roles after pausing + +## Price Adjustment Algorithm + +``` +function adjustPrice(redundancy): + if (contract is paused): + return false + + usedRedundancy = min(redundancy, targetRedundancy + maxConsideredExtraRedundancy) + currentRoundNum = currentRound() + + // Enforce once-per-round + if (currentRoundNum <= lastAdjustedRound): + revert PriceAlreadyAdjusted() + + skippedRounds = currentRoundNum - lastAdjustedRound - 1 + + // Apply change rate based on redundancy + changeRateIndex = usedRedundancy + newPrice = (changeRate[changeRateIndex] * currentPriceUpScaled) / priceBase + + // Apply maximum rate for skipped rounds + for each skipped round: + newPrice = (changeRate[0] * newPrice) / priceBase + + // Enforce minimum + if (newPrice < minimumPriceUpscaled): + newPrice = minimumPriceUpscaled + + currentPriceUpScaled = newPrice + lastAdjustedRound = currentRoundNum + + // Update PostageStamp + update PostageStamp price + emit PriceUpdate(currentPrice()) + return true +``` + +## Network Effects + +The price oracle creates a self-balancing system: + +1. **High redundancy** → Price decreases → Less new storage → Redundancy normalizes +2. **Low redundancy** → Price increases → More new storage → Redundancy normalizes +3. **Target redundancy** → Stable prices → Sustainable equilibrium + +This mechanism ensures the network maintains adequate data redundancy without manual intervention. + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..0614f6ae --- /dev/null +++ b/docs/README.md @@ -0,0 +1,75 @@ +# Swarm Smart Contracts Documentation + +This directory contains comprehensive documentation for the Swarm storage incentive smart contracts. + +## Documentation Structure + +### Core Contracts + +- **[Overview](./OVERVIEW.md)** - System architecture and overview +- **[PostageStamp](./POSTAGE_STAMP.md)** - Postage stamp batch management +- **[PriceOracle](./PRICE_ORACLE.md)** - Dynamic price oracle system +- **[StakeRegistry](./STAKING.md)** - Staking registry for node operators +- **[Redistribution](./REDISTRIBUTION.md)** - Schelling game for reserve commitment + +### Deployment + +- **[Deployment Guide](./DEPLOYMENT.md)** - How to deploy and configure the contracts + +## System Components + +### Token (`TestToken`) +- ERC20 token with 16 decimal places +- Used for staking and postage stamp purchases +- Deployed separately on mainnet + +### PostageStamp (`PostageStamp.sol`) +Manages postage stamp batches that users purchase to store chunks on Swarm. + +**Key Features:** +- Batch creation with depth and bucket depth +- Normalized balance tracking per chunk +- Expiration management via order statistics tree +- Role-based access control (Price Oracle, Redistributor, Pauser) + +### PriceOracle (`PriceOracle.sol`) +Automatically adjusts the price per chunk based on network redundancy. + +**Key Features:** +- Target redundancy: 4 (configurable) +- Price adjustment based on actual redundancy +- Minimum price enforcement +- Rounds of 152 blocks (~19 minutes at 5s/block) + +### StakeRegistry (`StakeRegistry.sol`) +Manages staking for node operators participating in the redistribution game. + +**Key Features:** +- Stake commitment and potential stake +- Overlay management for nodes +- Freeze and slash mechanisms for penalties +- Height-based reserve calculations + +### Redistribution (`Redistribution.sol`) +Implements the Schelling coordination game for reserve commitment consensus. + +**Key Features:** +- Three phases: Commit, Reveal, Claim +- Proximity-based participation +- Stochastic winner selection weighted by stake density +- Proof verification for chunk inclusion +- Automatic price adjustment feedback + +## Network Configuration + +- **Mainnet**: Chain ID 1, Swarm Network ID 1 +- **Testnet (Sepolia)**: Chain ID 11155111, Swarm Network ID 10 +- **Testnet Light**: Chain ID TBD, Swarm Network ID 5 +- **Tenderly**: For testing deployments + +## Quick Links + +- [Contracts on Etherscan](https://etherscan.io/) +- [Testnet Deployment Status](./DEPLOYMENT.md#deployment-status) +- [Mainnet Deployment Status](./DEPLOYMENT.md#deployment-status) + diff --git a/docs/REDISTRIBUTION.md b/docs/REDISTRIBUTION.md new file mode 100644 index 00000000..86b7edf8 --- /dev/null +++ b/docs/REDISTRIBUTION.md @@ -0,0 +1,556 @@ +# Redistribution Contract + +## Overview + +The `Redistribution` contract implements a Schelling coordination game for forming consensus around the Reserve Commitment (RC) hash. This is the core incentive mechanism that rewards nodes for storing data honestly. + +## Purpose + +The contract: +- Coordinates a three-phase game (Commit, Reveal, Claim) +- Form consensus on what chunks nodes are storing +- Randomly select winners who receive the PostageStamp pot +- Penalize nodes that reveal dishonest data +- Automatically adjust prices based on participation + +## Key Concepts + +### Schelling Coordination Game + +The game works because: +1. Nodes that store data honestly will have similar reserve commitments +2. This shared value becomes a "focal point" (Schelling point) +3. Nodes are incentivized to reveal the true value to maximize chances of winning +4. Nodes that lie can be caught and penalized + +### Three-Phase Design + +Each round consists of three consecutive phases: + +1. **Commit Phase** (25% = 38 blocks ≈ 3 minutes) + - Nodes commit to hashed values + - Cannot be decoded until reveal + +2. **Reveal Phase** (25% = 38 blocks ≈ 3 minutes) + - Nodes reveal their actual values + - Randomness updates after each reveal + - Only nodes in proximity to anchor can participate + +3. **Claim Phase** (50% = 76 blocks ≈ 6 minutes) + - Truth is determined from reveals + - Winner is randomly selected from truth-tellers + - Winner verifies their reserve + - Pot is transferred to winner + +### Proximity and Anchors + +**Anchor**: A random seed that determines which nodes are "in proximity" +**Proximity**: Two overlays are in proximity if their XOR is less than 2^(256-depth) + +```solidity +function inProximity(bytes32 A, bytes32 B, uint8 minimum) pure returns (bool) { + return uint256(A ^ B) < uint256(2 ** (256 - minimum)) +} +``` + +Higher depth = smaller neighborhood = more specific group + +### Round Structure + +```solidity +uint256 private constant ROUND_LENGTH = 152 blocks; // ~12.7 minutes at 5s/block + +// Phase checks +function currentPhaseCommit() { + return block.number % ROUND_LENGTH < ROUND_LENGTH / 4; +} + +function currentPhaseReveal() { + uint256 n = block.number % ROUND_LENGTH; + return n >= ROUND_LENGTH / 4 && n < ROUND_LENGTH / 2; +} + +function currentPhaseClaim() { + return block.number % ROUND_LENGTH >= ROUND_LENGTH / 2; +} +``` + +## Functions + +### Commit Phase Functions + +#### commit() +Commits to an obfuscated hash for the current round. + +**Parameters**: +- `_obfuscatedHash`: Hash of (overlay, depth, hash, nonce) +- `_roundNumber`: Round number for this commit + +**Requirements**: +- Must be in commit phase +- Node must be staked for 2+ rounds +- Node must not have already committed +- Not in last block of commit phase (prevents front-running) + +**Logic**: +```solidity +bytes32 overlay = get from StakeRegistry +uint256 stake = get effective stake from StakeRegistry +uint8 height = get from StakeRegistry +// Check 2-round staking requirement +// Store commit with obfuscated hash +``` + +**Commit Structure**: +```solidity +struct Commit { + bytes32 overlay; + address owner; + bool revealed; + uint8 height; + uint256 stake; + bytes32 obfuscatedHash; + uint256 revealIndex; +} +``` + +#### isParticipatingInUpcomingRound() +Checks if node is eligible for NEXT round's commit phase. + +**Parameters**: +- `_owner`: Node address +- `_depth`: Intended storage depth + +**Returns**: True if node's overlay is in proximity to NEXT round's anchor + +**Use**: Called during reveal/claim phases to check next round eligibility + +### Reveal Phase Functions + +#### reveal() +Reveals the actual values used to create a commit. + +**Parameters**: +- `_depth`: Reported storage depth +- `_hash`: Reserve commitment hash +- `_revealNonce`: Nonce used in commit + +**Requirements**: +- Must be in reveal phase +- Anchor must be in range of reported depth +- Commit must exist and match + +**Logic**: +```solidity +// Calculate obfuscated hash from inputs +bytes32 obfuscatedHash = wrapCommit(overlay, _depth, _hash, _revealNonce) +// Find matching commit +// Check proximity to anchor +// Store reveal +``` + +**First Reveal Special Handling**: +- Sets `currentRevealRoundAnchor` from seed +- Initializes reveal array +- Updates randomness + +**Reveal Structure**: +```solidity +struct Reveal { + bytes32 overlay; + address owner; + uint8 depth; + uint256 stake; + uint256 stakeDensity; // stake * 2^(depth - height) + bytes32 hash; +} +``` + +**Stake Density**: Weighted stake based on reported depth +Higher depth → Higher density → Better chance of being selected as truth + +### Claim Phase Functions + +#### claim() +Winner claims the pot by proving they have the chunks. + +**Parameters**: +- `entryProof1`: Chunk inclusion proof for random index 1 +- `entryProof2`: Chunk inclusion proof for random index 2 +- `entryProofLast`: Chunk inclusion proof for last index + +**Requirements**: +- Only winner can call +- Must be in claim phase +- Must provide valid proofs + +**Logic**: +1. Select winner (if not already done) +2. Calculate random chunk indices from seed +3. Verify proximity for all three chunks +4. Verify inclusion proofs for all three chunks +5. Verify stamp proofs for all chunks +6. Verify SOC proofs (if applicable) +7. Check ordering of chunks +8. Estimate reserve size +9. Withdraw pot from PostageStamp +10. Transfer to winner + +#### isWinner() +Determines if caller is the winner for the current round. + +**Returns**: True if caller's overlay matches the selected winner + +**Logic**: Same winner selection as `claim()` but without doing actions + +### Admin Functions + +#### setFreezingParams() +Sets the penalty multipliers. + +**Parameters**: +- `_penaltyMultiplierDisagreement`: Freeze duration multiplier for disagreeing +- `_penaltyMultiplierNonRevealed`: Freeze duration multiplier for not revealing +- `_penaltyRandomFactor`: Random factor for disagreement penalty (0-100) + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call + +#### setSampleMaxValue() +Changes the maximum value for reserve size estimation. + +**Parameters**: +- `_sampleMaxValue`: New maximum value + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call + +#### pause() / unPause() +Pauses or unpauses the contract. + +### View Functions + +#### currentRound() +Returns current round number: `block.number / ROUND_LENGTH` + +#### currentPhaseCommit() / currentPhaseReveal() / currentPhaseClaim() +Returns true if in respective phase + +#### isParticipatingInUpcomingRound(address, uint8) +Checks eligibility for next round + +#### currentRoundAnchor() +Returns the anchor for the current phase (proximity calculation) + +#### inProximity(bytes32, bytes32, uint8) +Checks if two overlays are within proximity + +#### currentRevealRoundAnchor +The anchor set during first reveal + +#### seed +Current random seed (updated after each reveal) + +## Proof Verification + +### Chunk Inclusion Proof + +Verifies that a chunk is included in a Merkle tree (BMT - Binary Merkle Tree). + +**Structure**: +```solidity +struct ChunkInclusionProof { + bytes32[] proofSegments; // Merkle proof segments + bytes32 proveSegment; // Chunk data + bytes32[] proofSegments2; // Proof for transformed address + bytes32 proveSegment2; // Transformed chunk + uint64 chunkSpan; // Size of chunk span + bytes32[] proofSegments3; // Proof for transformed chunk + PostageProof postageProof; // Postage stamp proof + SOCProof[] socProof; // Single-owner chunk proof +} +``` + +### Postage Proof + +Verifies postage stamp validity for a chunk. + +**Structure**: +```solidity +struct PostageProof { + bytes signature; // Batch owner signature + bytes32 postageId; // Batch ID + uint64 index; // Stamp index + uint64 timeStamp; // Timestamp +} +``` + +### SOC Proof + +Verifies single-owner chunk ownership. + +**Structure**: +```solidity +struct SOCProof { + address signer; // Ethereum address of signer + bytes signature; // Signature + bytes32 identifier; // Content identifier + bytes32 chunkAddr; // Chunk address +} +``` + +## Winner Selection Algorithm + +### Truth Selection (from reveals) + +```solidity +function getCurrentTruth() { + currentSum = 0 + for (each revealed commit in order) { + currentSum += reveal.stakeDensity + if (random < reveal.stakeDensity / currentSum) { + truthHash = reveal.hash + truthDepth = reveal.depth + } + } + return (truthHash, truthDepth) +} +``` + +The **median reveal** (by stake density) is selected as truth. + +### Winner Selection (from truth-tellers) + +```solidity +function winnerSelection() { + (truthHash, truthDepth) = getCurrentTruth() + currentSum = 0 + redundancyCount = 0 + for (each reveal matching truth) { + currentSum += reveal.stakeDensity + if (random < reveal.stakeDensity / currentSum) { + winner = reveal + } + redundancyCount++ + } + adjustPrice(redundancyCount) + return winner +} +``` + +A **single winner** is randomly selected from truth-tellers, weighted by stake density. + +## Penalty System + +### Non-Reveal Penalty + +Nodes that commit but don't reveal are penalized: + +```solidity +freezeDeposit(committer, penaltyMultiplierNonRevealed * ROUND_LENGTH * 2^truthDepth) +``` + +### Disagreement Penalty + +Nodes that reveal wrong truth are penalized (randomly): + +```solidity +if (revealed but wrong truth && random(100) < penaltyRandomFactor) { + freezeDeposit(revealer, penaltyMultiplierDisagreement * ROUND_LENGTH * 2^truthDepth) +} +``` + +### Depth-Based Scaling + +Penalties scale exponentially with reported depth: +- Depth 20: 1x freeze duration +- Depth 21: 2x freeze duration +- Depth 22: 4x freeze duration +- etc. + +## Price Adjustment Integration + +After each claim phase, the contract calls: +```solidity +OracleContract.adjustPrice(uint16(redundancyCount)) +``` + +The `redundancyCount` is the number of nodes that revealed the correct truth, which becomes the input for price adjustment. + +## Events + +```solidity +event Committed(uint256 roundNumber, bytes32 overlay, uint8 height); +event Revealed(uint256 roundNumber, bytes32 overlay, uint256 stake, + uint256 stakeDensity, bytes32 reserveCommitment, uint8 depth); +event WinnerSelected(Reveal winner); +event TruthSelected(bytes32 hash, uint8 depth); +event ChunkCount(uint256 validChunkCount); +event CurrentRevealAnchor(uint256 roundNumber, bytes32 anchor); +event PriceAdjustmentSkipped(uint16 redundancyCount); +event WithdrawFailed(address owner); +``` + +## Deployment Configuration + +```typescript +constructor( + address staking, + address postageContract, + address oracleContract +) +``` + +- `staking`: StakeRegistry address +- `postageContract`: PostageStamp address +- `oracleContract`: PriceOracle address + +## Round Lifecycle Example + +### Round N: Block 152000 + +**Commit Phase (152000-152037)**: +``` +Block 152000: Node A commits hash_1 +Block 152001: Node B commits hash_2 +Block 152037: Commit phase ends +``` + +**Reveal Phase (152038-152075)**: +``` +Block 152038: First node reveals + → currentRevealRoundAnchor = currentSeed() + → updateRandomness() +Block 152039: Node B reveals + → updateRandomness() +Block 152075: Reveal phase ends +``` + +**Claim Phase (152076-152151)**: +``` +Block 152076: Node A checks isWinner() +Block 152100: Winner claims pot + → truth = getCurrentTruth() + → winner = winnerSelection() + → verify proofs + → withdraw pot + → adjustPrice() +``` + +### Round N+1: Block 152152 + +**Commit Phase (152152-152189)**: +- Uses anchor from seed at block 152152 +- Different nodes participate (based on proximity) + +## Proof Verification Details + +### Inclusion Proof Verification + +For each chunk in the claim: +1. Verify chunk is in proximity to anchor +2. Verify chunk address matches reserve commitment hash +3. Verify chunk is in transformed address tree +4. Verify chunks are ordered correctly (first < second < last) +5. Verify reserve size estimation + +### Stamp Verification + +1. Check batch exists and is alive +2. Verify stamp index is valid for batch depth +3. Verify stamp bucket matches chunk bucket +4. Verify batch owner signature on chunk + +### SOC Verification + +1. Verify signature matches signer +2. Verify SOC address calculation matches chunk address +3. Handle transformed addresses for SOCs + +## Error Codes + +```solidity +error NotCommitPhase(); // Wrong phase +error NoCommitsReceived(); // No commits in round +error AlreadyCommitted(); // Already committed this round +error MustStake2Rounds(); // Need to stake 2 rounds first +error NotStaked(); // Not staked +error NotRevealPhase(); // Wrong phase +error OutOfDepthReveal(bytes32); // Anchor out of depth +error AlreadyRevealed(); // Already revealed +error NotClaimPhase(); // Wrong phase +error AlreadyClaimed(); // Round already claimed +error SocVerificationFailed(bytes32); // SOC verification failed +error IndexOutsideSet(bytes32); // Stamp index invalid +error SigRecoveryFailed(bytes32); // Signature recovery failed +error BatchDoesNotExist(bytes32); // Batch not found +error BucketDiffers(bytes32); // Bucket mismatch +error InclusionProofFailed(uint8, bytes32); // Inclusion proof failed +error RandomElementCheckFailed(); // Chunk order wrong +error LastElementCheckFailed(); // Last element order wrong +error ReserveCheckFailed(bytes32); // Reserve size too large +``` + +## Examples + +### Committing + +```solidity +bytes32 overlay = StakeRegistry(stakes).overlayOfAddress(myAddress); +bytes32 hash = calculateReserveCommitment(); // From stored chunks +bytes32 nonce = randomNonce(); + +bytes32 obfuscatedHash = Redistribution(redis).wrapCommit( + overlay, + depth, + hash, + nonce +); + +Redistribution(redis).commit(obfuscatedHash, currentRound()); +``` + +### Revealing + +```solidity +Redistribution(redis).reveal( + reportedDepth, + reserveCommitment, + revealNonce +); +``` + +### Checking if Winner + +```solidity +bool winner = Redistribution(redis).isWinner(overlay); +if (winner) { + // Generate proofs and claim + claim(proof1, proof2, proofLast); +} +``` + +### Checking Eligibility + +```solidity +bool eligible = Redistribution(redis).isParticipatingInUpcomingRound( + myAddress, + intendedDepth +); +``` + +## Security Considerations + +1. **Random Nonce**: Must be truly random and never reused +2. **Proximity Calculations**: Proper depth responsibility +3. **Freeze Protection**: Prevents stake manipulation during freeze +4. **Proof Verification**: Comprehensive validation prevents fake claims +5. **Random Selection**: Weighted fairly by stake density +6. **Truth Selection**: Deters dishonest behavior + +## Related Contracts + +- **StakeRegistry**: Provides stake and overlay info +- **PostageStamp**: Source of pot, valid chunk count +- **PriceOracle**: Receives redundancy data for price adjustment + diff --git a/docs/STAKING.md b/docs/STAKING.md new file mode 100644 index 00000000..caf745d7 --- /dev/null +++ b/docs/STAKING.md @@ -0,0 +1,427 @@ +# StakeRegistry Contract + +## Overview + +The `StakeRegistry` (Staking) contract manages staking for node operators participating in the Swarm network's redistribution game. Nodes stake tokens to become eligible for rewards and penalties. + +## Purpose + +The contract: +- Tracks node stakes with overlay addresses +- Manages committed vs potential stake +- Allows height-based reserve calculations +- Provides freeze/slash mechanisms for penalties +- Enables stake withdrawal for surplus amounts + +## Key Concepts + +### Overlay Address + +Each node has an "overlay address" which is derived from: +```solidity +overlay = keccak256(abi.encodePacked(nodeAddress, reverse(networkId), nonce)) +``` + +This creates a unique identifier for the node within a specific Swarm network. + +### Two-Stake System + +The contract maintains two types of stake: + +1. **Committed Stake**: Chunks pledged to store (in oracle price units) +2. **Potential Stake**: Actual BZZ tokens staked + +The effective stake (used in redistribution) is the minimum of: +```solidity +effectiveStake = min( + committedStake * price * 2^height, + potentialStake +) +``` + +### Height Parameter + +The `height` parameter allows nodes to register additional capacity: +- Height 0: Normal capacity (committed stake * price) +- Height 1: Double capacity (committed stake * price * 2) +- Height 2: 4x capacity, etc. + +This allows nodes to increase their effective stake without depositing more tokens by registering additional storage space. + +## Functions + +### Node Functions + +#### manageStake() +Creates or updates a node's stake, optionally changing overlay. + +**Parameters**: +- `_setNonce`: Nonce for overlay calculation +- `_addAmount`: Additional BZZ tokens to add (0 if only changing overlay) +- `_height`: Height multiplier (0-255) + +**Requirements**: +- Minimum stake: `_addAmount >= MIN_STAKE * 2^height` (first deposit only) +- If frozen: transaction reverts with `Frozen()` error + +**Logic**: +1. Calculate new overlay from nonce +2. If first stake: check minimum deposit requirement +3. If frozen: revert (can't change stake while frozen) +4. Update potential stake if depositing +5. Calculate new committed stake: `potentialStake / (price * 2^height)` +6. Never allow committed stake to decrease +7. Transfer tokens if depositing +8. Store new stake state +9. Emit events + +**Overlay Change**: +If overlay changes, emits `OverlayChanged` event (useful for monitoring). + +#### withdrawFromStake() +Withdraws surplus stake (difference between potential and effective stake). + +**Requirements**: +- No special roles needed (only withdraws surplus) + +**Logic**: +```solidity +surplus = potentialStake - effectiveStake +if (surplus > 0) { + transfer tokens to node + potentialStake -= surplus +} +``` + +**Use Case**: If price increases or height decreases, effective stake may be less than potential, allowing withdrawal of the difference. + +#### migrateStake() +Emergency withdrawal when contract is paused. + +**Requirements**: +- Contract must be paused +- Withdraws entire potential stake + +**Use Case**: For upgrading to new staking contracts. + +### Redistributor Functions + +#### freezeDeposit() +Freezes a node's stake for a specified time (penalty). + +**Parameters**: +- `_owner`: Node address to freeze +- `_time`: Duration in blocks + +**Requirements**: +- Only `REDISTRIBUTOR_ROLE` can call + +**Logic**: +```solidity +stakes[_owner].lastUpdatedBlockNumber = block.number + _time +``` + +While frozen: `stakes[_owner].lastUpdatedBlockNumber > block.number` + +**Effects**: +- Node cannot call `manageStake()` while frozen +- `nodeEffectiveStake()` returns 0 while frozen +- After freeze expires, can resume normal operations + +#### slashDeposit() +Slashes (removes) a specified amount from a node's stake. + +**Parameters**: +- `_owner`: Node address to slash +- `_amount`: BZZ amount to remove + +**Requirements**: +- Only `REDISTRIBUTOR_ROLE` can call + +**Logic**: +```solidity +if (potentialStake > _amount) { + potentialStake -= _amount + lastUpdatedBlockNumber = block.number +} else { + delete stakes[_owner] // Remove entire stake +} +``` + +**Use Cases**: +- Severe protocol violations +- Currently not actively used (freezing is preferred) + +### Admin Functions + +#### changeNetworkId() +Changes the Swarm network ID. + +**Parameters**: +- `_NetworkId`: New network ID + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call + +**Effects**: +- New overlays will use new network ID +- Existing overlays remain valid + +#### pause() / unPause() +Pauses or unpauses the contract. + +**Requirements**: +- Only `DEFAULT_ADMIN_ROLE` can call + +**Effects**: +- Prevents `manageStake()` calls +- Allows `migrateStake()` calls + +### View Functions + +#### nodeEffectiveStake(address) +Returns the effective stake used in redistribution game. + +```solidity +if (addressNotFrozen(address)) { + return calculateEffectiveStake( + committedStake, + potentialStake, + height + ) +} else { + return 0 +} +``` + +#### withdrawableStake() +Returns the amount of surplus stake that can be withdrawn. + +#### lastUpdatedBlockNumberOfAddress(address) +Returns when stake was last updated (used to check if frozen). + +#### overlayOfAddress(address) +Returns the current overlay for a node. + +#### heightOfAddress(address) +Returns the height multiplier for a node. + +### Internal Functions + +#### calculateEffectiveStake() +Calculates effective stake based on committed stake and height. + +```solidity +committedStakeBzz = (2^height) * committedStake * oracle.currentPrice() +return min(committedStakeBzz, potentialStake) +``` + +#### addressNotFrozen() +Checks if a node is frozen: +```solidity +return stakes[_owner].lastUpdatedBlockNumber < block.number +``` + +#### reverse() +Byte-reverses a uint64 (for network ID in overlay calculation). + +## Stake Structure + +```solidity +struct Stake { + bytes32 overlay; // Node's overlay address + uint256 committedStake; // Chunks pledged + uint256 potentialStake; // BZZ tokens staked + uint256 lastUpdatedBlockNumber; // Update timestamp / freeze flag + uint8 height; // Reserve height multiplier +} +``` + +## Events + +```solidity +event StakeUpdated( + address indexed owner, + uint256 committedStake, + uint256 potentialStake, + bytes32 overlay, + uint256 lastUpdatedBlock, + uint8 height +); + +event OverlayChanged(address owner, bytes32 overlay); + +event StakeSlashed(address slashed, bytes32 overlay, uint256 amount); + +event StakeFrozen(address frozen, bytes32 overlay, uint256 time); + +event StakeWithdrawn(address node, uint256 amount); +``` + +## Roles + +- **DEFAULT_ADMIN_ROLE**: Change network ID, pause/unpause +- **REDISTRIBUTOR_ROLE**: Freeze and slash stakes (typically Redistribution contract) + +## Deployment Configuration + +```typescript +constructor(address _bzzToken, uint64 _NetworkId, address _oracleContract) +``` + +- `_bzzToken`: ERC20 token address for staking +- `_NetworkId`: Swarm network ID (1 for mainnet, 10 for testnet) +- `_oracleContract`: PriceOracle address for price queries + +## Constants + +```solidity +uint64 private constant MIN_STAKE = 100000000000000000; // 0.1 BZZ +``` + +## Stake Lifecycle + +### 1. Initial Stake + +```solidity +// Node calls with initial deposit +manageStake(nonce, 1000000000000000000, 1) +// Deposits 1 BZZ, sets height to 1 + +// At price 1000 chunks/BZZ, height 1: +// committedStake = 1000000000000000000 / (1000 * 2^1) = 500000 chunks +// effectiveStake = min(500000 * 1000 * 2, 1000000000000000000) = 1000000000000000000 +``` + +### 2. Stake Update + +```solidity +// Add more tokens +manageStake(nonce, 500000000000000000, 1) +// Deposits 0.5 BZZ more + +// Recalculate: +// potentialStake = 1500000000000000000 +// committedStake = 1500000000000000000 / (1000 * 2^1) = 750000 chunks +// effectiveStake = min(750000 * 1000 * 2, 1500000000000000000) = 1500000000000000000 +``` + +### 3. Surplus Withdrawal + +```solidity +// Price increased from 1000 to 1200 chunks/BZZ +// committedStake = 750000 chunks +// effectiveStake = min(750000 * 1200 * 2, 1500000000000000000) = 1800000000000000000 +// But actual potentialStake = 1500000000000000000 +// Can withdraw: 0 (effective = potential) + +// OR height decreased from 1 to 0 +// effectiveStake = min(750000 * 1200 * 1, 1500000000000000000) = 900000000000000000 +// Can withdraw: 1500000000000000000 - 900000000000000000 = 600000000000000000 +``` + +### 4. Penalty Freeze + +```solidity +// Redistribution calls after node violates protocol +freezeDeposit(nodeAddress, 1000 blocks) + +// While frozen: +// nodeEffectiveStake() returns 0 +// manageStake() reverts with Frozen() +``` + +## Integration with Redistribution + +The `nodeEffectiveStake()` value is used in the redistribution game to: +1. Weight commit selection during truth consensus +2. Calculate stake density for winner selection +3. Determine eligibility for participation + +## Examples + +### Creating a Stake + +```solidity +// Approve tokens first +ERC20(bzzToken).approve(stakeRegistry, 2000000000000000000); + +// Create stake +StakeRegistry(stakeRegistry).manageStake( + keccak256("my-nonce"), // nonce + 2000000000000000000, // 2 BZZ + 2 // height = 2 (4x capacity) +); +``` + +### Checking Stake Status + +```solidity +uint256 effective = StakeRegistry(stakeRegistry).nodeEffectiveStake(myAddress); +bytes32 overlay = StakeRegistry(stakeRegistry).overlayOfAddress(myAddress); +uint8 height = StakeRegistry(stakeRegistry).heightOfAddress(myAddress); +bool isFrozen = StakeRegistry(stakeRegistry).lastUpdatedBlockNumberOfAddress(myAddress) > block.number; +``` + +### Withdrawing Surplus + +```solidity +uint256 surplus = StakeRegistry(stakeRegistry).withdrawableStake(); +if (surplus > 0) { + StakeRegistry(stakeRegistry).withdrawFromStake(); +} +``` + +### Changing Overlay + +```solidity +// Change overlay without depositing +StakeRegistry(stakeRegistry).manageStake( + keccak256("new-nonce"), // new nonce + 0, // no additional deposit + 2 // keep same height +); +``` + +## Error Codes + +```solidity +error TransferFailed(); // Token transfer failed +error Frozen(); // Node is frozen +error Unauthorized(); // Only admin +error OnlyRedistributor(); // Only redistributor role +error OnlyPauser(); // Only pauser role +error BelowMinimumStake(); // First deposit below minimum +error DecreasedCommitment(); // Committed stake cannot decrease +``` + +## Security Considerations + +1. **Minimum Stake**: Prevents dust attacks +2. **Non-Decreasing Commitment**: Prevents gaming the system +3. **Freeze Mechanism**: Temporary penalty without full slash +4. **Pausability**: Emergency stop with migration path +5. **Frozen Check**: Prevents stake modifications during penalty + +## Overlay Calculation + +The `reverse()` function byte-reverses the network ID for the overlay calculation. This is done for endianness consistency between different systems that calculate overlays. + +```solidity +function reverse(uint64 input) internal pure returns (uint64 v) { + v = input; + // swap bytes + v = ((v & 0xFF00FF00FF00FF00) >> 8) | ((v & 0x00FF00FF00FF00FF) << 8); + // swap 2-byte long pairs + v = ((v & 0xFFFF0000FFFF0000) >> 16) | ((v & 0x0000FFFF0000FFFF) << 16); + // swap 4-byte long pairs + v = (v >> 32) | (v << 32); +} +``` + +## Related Contracts + +- **Token**: ERC20 token used for staking +- **PriceOracle**: Provides current price for calculations +- **Redistribution**: Uses effective stake for game participation +