Some checks failed
contracts-ci / Install (lockfile) → compile → full test suite (push) Has been cancelled
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Has been cancelled
contracts-ci / PQC known-answer tests (NIST vectors) (push) Has been cancelled
contracts-ci / Coverage (scoped, with artifacts) (push) Has been cancelled
The published line and the local line had no common ancestor: the public one carried the redaction pass, the local one carried three weeks of corrections that never shipped. This commit ports the local work onto the public line, keeps every public redaction, and extends the same discretion to seven client mentions that were still named in published comments. Carried: LICENSE year and LICENSING.md; the measured burn figures replacing the deflation claim (the vault holds ~0.137 AERE of 2.8 billion, and burn is a share of validator coinbase revenue, which is zero today); 'audited' removed from next to Bouncy Castle; citation paths rewritten to published form with CITATIONS-UNRESOLVED.md remeasured 2026-08-11; VERIFY-POLICY.md; slashing and ownership comments brought down to what the code does; the AerePyth repair; the shutter test helper the tests cite; runnable package.json entries; the CI file split into a GitHub/Gitea twin pair with a real measured test-run status; and the .gitignore hardening written after a compiled artifact leaked a local path in a sibling repository. A false '2-of-3 multisig' description of the owner account is corrected to what the chain measures: an externally owned account. The self-audit findings catalog stays unpublished pending an explicit decision.
197 lines
8.6 KiB
Solidity
197 lines
8.6 KiB
Solidity
// 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 Subscription accounting contract. Users pay a recurring AERE fee and
|
|
* accrue a payout computed from owner-configured parameters, settled
|
|
* from a Foundation-funded pool held by this contract.
|
|
*
|
|
* @dev NOT OFFERED, NOT DEPLOYED, AND NOT COHERENT ON THIS CHAIN. Read this
|
|
* before reading anything else in the file.
|
|
*
|
|
* 1. There is no mining on Aere. Chain 2800 is Hyperledger Besu QBFT
|
|
* proof-of-authority with N=7 Foundation-operated validators. There is
|
|
* no hash rate, no difficulty and no mining reward. The `hashRateThs`,
|
|
* `networkHashRateThs` and `difficultyAdjustmentBps` fields below
|
|
* therefore do not measure anything that exists. They are owner-set
|
|
* numbers that feed an owner-set payout formula.
|
|
* 2. Because of (1), the payout is not a share of mining proceeds. It is a
|
|
* figure the operator chooses, funded by Foundation top-ups and by
|
|
* subscriber payments held on this contract. Nothing in the code
|
|
* constrains payouts to any external revenue.
|
|
* 3. No guarantee of any allocation, payout, rate or value is made by this
|
|
* contract or by the Aere Foundation. Subscription payments are not an
|
|
* investment, confer no claim on protocol revenue, and may return
|
|
* nothing. `claim()` reverts when the pool is empty.
|
|
* 4. MEASURED, searched across all published repositories: no deployment
|
|
* address for this contract appears anywhere in the published set. It
|
|
* is source-only here.
|
|
*
|
|
* This contract is retained in the repository as a historical artifact of
|
|
* an earlier design and is pending a Foundation decision on whether it
|
|
* should be published at all. It should not be deployed, offered or
|
|
* referenced as a product in its current form.
|
|
*/
|
|
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 section 5.3):
|
|
* R = B x HR x (1 - Dt) x T
|
|
* where HR = userHashRate / networkHashRate
|
|
*
|
|
* Every input to this formula is owner-configured and none of them
|
|
* corresponds to a physical quantity on a proof-of-authority chain.
|
|
* See the NOT OFFERED, NOT DEPLOYED note at the top of this file. The
|
|
* value returned here is not a guaranteed, promised or expected
|
|
* return.
|
|
*/
|
|
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 {}
|
|
}
|