// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; /** * @title AereMiningSubscription (AireFlow) * @notice Cloud mining subscriptions paid in AERE * @dev Whitepaper §5.3 — "Revolutionary Subscription Mining Platform" * Users pay a monthly AERE fee for guaranteed hash rate allocation, * receive proportional block rewards distributed by the foundation. * * Reference whitepaper §5.3: * AireFlow 15.0 TH/s example: $300/month → ~30 AERE/month rewards (illustrative). */ contract AereMiningSubscription is Ownable, ReentrancyGuard { enum Tier { BASIC, PREMIUM, ENTERPRISE, AIREFLOW } struct TierConfig { uint256 monthlyPriceAere; // wei uint256 hashRateThs; // TH/s allocated bool active; } struct Subscription { Tier tier; uint256 startBlock; uint256 expiresAt; // block.timestamp uint256 totalPaid; uint256 unclaimedRewards; uint256 lastRewardBlock; } mapping(Tier => TierConfig) public tiers; mapping(address => Subscription) public subscriptions; address[] public subscribers; mapping(address => uint256) private subscriberIndex; uint256 public constant SUBSCRIPTION_PERIOD = 30 days; uint256 public networkHashRateThs = 2500; // bootstrap value, governance-updatable uint256 public blockReward = 1.25 ether; // matches whitepaper §5.3 (1.25 AERE/block) uint256 public difficultyAdjustmentBps = 10750; // Dt = 1.075 example address public foundationTreasury; event TierConfigured(Tier indexed tier, uint256 priceAere, uint256 hashRateThs); event Subscribed(address indexed user, Tier tier, uint256 expiresAt); event RewardsAccrued(address indexed user, uint256 amount); event RewardsClaimed(address indexed user, uint256 amount); event TreasurySet(address indexed treasury); constructor(address _foundationTreasury) { foundationTreasury = _foundationTreasury; // Default tier configuration (whitepaper-aligned, governance-mutable) tiers[Tier.BASIC] = TierConfig({monthlyPriceAere: 10 ether, hashRateThs: 1, active: true}); tiers[Tier.PREMIUM] = TierConfig({monthlyPriceAere: 50 ether, hashRateThs: 6, active: true}); tiers[Tier.ENTERPRISE] = TierConfig({monthlyPriceAere: 200 ether, hashRateThs: 30, active: true}); tiers[Tier.AIREFLOW] = TierConfig({monthlyPriceAere: 100 ether, hashRateThs: 15, active: true}); } function configureTier(Tier tier, uint256 priceAere, uint256 hashRateThs) external onlyOwner { tiers[tier] = TierConfig({monthlyPriceAere: priceAere, hashRateThs: hashRateThs, active: true}); emit TierConfigured(tier, priceAere, hashRateThs); } function setTreasury(address _treasury) external onlyOwner { foundationTreasury = _treasury; emit TreasurySet(_treasury); } function setNetworkHashRate(uint256 _ths) external onlyOwner { require(_ths > 0, "AereMining: hashrate>0"); networkHashRateThs = _ths; } /** * @notice Subscribe (or extend) to a mining tier. Pay msg.value in native AERE. */ function subscribe(Tier tier) external payable nonReentrant { TierConfig memory cfg = tiers[tier]; require(cfg.active, "AereMining: tier inactive"); require(msg.value >= cfg.monthlyPriceAere, "AereMining: insufficient payment"); Subscription storage s = subscriptions[msg.sender]; // Settle pending rewards before changing tier/period if (s.tier == tier && s.expiresAt > block.timestamp) { _accrue(msg.sender); } else { // New tier or expired — reset rewards baseline s.lastRewardBlock = block.number; } if (s.expiresAt < block.timestamp) { s.startBlock = block.number; s.expiresAt = block.timestamp + SUBSCRIPTION_PERIOD; } else { s.expiresAt += SUBSCRIPTION_PERIOD; } s.tier = tier; s.totalPaid += msg.value; // Track unique subscribers if (subscriberIndex[msg.sender] == 0) { subscribers.push(msg.sender); subscriberIndex[msg.sender] = subscribers.length; } // Forward payment to foundation treasury (bool ok, ) = foundationTreasury.call{value: msg.value}(""); require(ok, "AereMining: treasury transfer failed"); emit Subscribed(msg.sender, tier, s.expiresAt); } /** * @notice Pending rewards (in AERE wei) for a subscriber. * @dev Formula (whitepaper §5.3): * R = B × HR × (1 - Dt) × T * where HR = userHashRate / networkHashRate */ function pendingRewards(address user) public view returns (uint256) { Subscription memory s = subscriptions[user]; if (s.expiresAt < block.timestamp || s.lastRewardBlock == 0) return s.unclaimedRewards; uint256 blocksElapsed = block.number - s.lastRewardBlock; uint256 userHashRate = tiers[s.tier].hashRateThs; // Compute (1 - Dt). difficultyAdjustmentBps is e.g. 10750 = 1.075. // (1 - Dt) is negative if Dt > 1, but in the whitepaper formula difficulty // damps reward, not amplifies. Use (10000 - (difficultyAdjustmentBps - 10000)) // capped to non-negative if Dt < 2. int256 difficultyFactor = int256(20000) - int256(difficultyAdjustmentBps); if (difficultyFactor < 0) difficultyFactor = 0; uint256 dampedReward = (blockReward * uint256(difficultyFactor)) / 10000; uint256 reward = (dampedReward * userHashRate * blocksElapsed) / networkHashRateThs; return s.unclaimedRewards + reward; } function _accrue(address user) internal { uint256 reward = pendingRewards(user); Subscription storage s = subscriptions[user]; if (reward > s.unclaimedRewards) { s.unclaimedRewards = reward; emit RewardsAccrued(user, reward); } s.lastRewardBlock = block.number; } function claim() external nonReentrant { _accrue(msg.sender); Subscription storage s = subscriptions[msg.sender]; uint256 amount = s.unclaimedRewards; require(amount > 0, "AereMining: nothing to claim"); s.unclaimedRewards = 0; require(address(this).balance >= amount, "AereMining: pool insufficient - needs foundation top-up"); (bool ok, ) = msg.sender.call{value: amount}(""); require(ok, "AereMining: transfer failed"); emit RewardsClaimed(msg.sender, amount); } function subscriberCount() external view returns (uint256) { return subscribers.length; } /// @notice Allow foundation to top up the reward pool receive() external payable {} }