Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
136 lines
5.8 KiB
Solidity
136 lines
5.8 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.23;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
|
|
interface IBurnVault {
|
|
function burn() external payable;
|
|
}
|
|
|
|
/**
|
|
* @title AereCoinbaseSplitter
|
|
* @notice Routes validator coinbase rewards through an atomic split:
|
|
* burnBps to AereFeeBurnVault (default 3750 = 37.5%, matching whitepaper §3.3),
|
|
* remainder back to the validator's address.
|
|
*
|
|
* Validators (or their forwarder daemons) call `splitAndDistribute()` with
|
|
* their accumulated coinbase as msg.value. The contract atomically:
|
|
* 1. Sends burnBps/10000 of msg.value to AereFeeBurnVault.
|
|
* 2. Sends the remainder back to the validator's address.
|
|
* 3. Emits Burned + Distributed events with cumulative totals.
|
|
*
|
|
* Anyone can call this contract — there is no allowlist of "who can burn."
|
|
* The splitter is owner-configurable only for the burnBps parameter and
|
|
* the burn-vault address; it cannot hold or steal funds.
|
|
*
|
|
* Cumulative on-chain accounting allows /network-status to display a live
|
|
* burn rate, and any third-party explorer or analyst to verify the
|
|
* whitepaper §3.3 deflation claim in O(1) reads.
|
|
*/
|
|
contract AereCoinbaseSplitter is Ownable, ReentrancyGuard {
|
|
/// AereFeeBurnVault — where the burned portion goes.
|
|
address payable public burnVault;
|
|
|
|
/// Burn rate in basis points. 3750 = 37.5% (matches whitepaper §3.3).
|
|
uint256 public burnBps = 3750;
|
|
uint256 public constant BPS = 10_000;
|
|
|
|
/// Cumulative AERE burned through this splitter (lifetime).
|
|
uint256 public totalBurned;
|
|
/// Cumulative AERE distributed back to validators (lifetime).
|
|
uint256 public totalDistributed;
|
|
/// Per-validator (or per-caller) cumulative burn contribution.
|
|
mapping(address => uint256) public burnedBy;
|
|
mapping(address => uint256) public distributedTo;
|
|
|
|
event Burned(address indexed contributor, uint256 amount, uint256 newTotalBurned);
|
|
event Distributed(address indexed validator, uint256 amount, uint256 newTotalDistributed);
|
|
event BurnBpsChanged(uint256 oldBps, uint256 newBps);
|
|
event BurnVaultChanged(address oldVault, address newVault);
|
|
|
|
constructor(address payable _burnVault) {
|
|
require(_burnVault != address(0), "Splitter: zero vault");
|
|
burnVault = _burnVault;
|
|
}
|
|
|
|
// ───────────────────── Admin ─────────────────────
|
|
|
|
/// Foundation can adjust the burn rate up to a hard cap of 50% — never higher.
|
|
/// The whitepaper says "up to 37.5%" so this provides headroom but caps abuse.
|
|
function setBurnBps(uint256 _bps) external onlyOwner {
|
|
require(_bps <= 5000, "Splitter: bps too high");
|
|
emit BurnBpsChanged(burnBps, _bps);
|
|
burnBps = _bps;
|
|
}
|
|
|
|
function setBurnVault(address payable _vault) external onlyOwner {
|
|
require(_vault != address(0), "Splitter: zero vault");
|
|
emit BurnVaultChanged(burnVault, _vault);
|
|
burnVault = _vault;
|
|
}
|
|
|
|
// ───────────────────── Core ─────────────────────
|
|
|
|
/**
|
|
* @notice Split msg.value: burnBps/10000 to AereFeeBurnVault, remainder to `validator`.
|
|
* @param validator The address to receive the unburned portion. For validator-
|
|
* initiated calls this is typically tx.origin or msg.sender;
|
|
* the caller decides where their rebate goes.
|
|
*/
|
|
function splitAndDistribute(address payable validator) external payable nonReentrant {
|
|
require(msg.value > 0, "Splitter: zero amount");
|
|
require(validator != address(0), "Splitter: zero validator");
|
|
|
|
uint256 burnAmount = (msg.value * burnBps) / BPS;
|
|
uint256 distributeAmount = msg.value - burnAmount;
|
|
|
|
// Send burnAmount to AereFeeBurnVault.
|
|
if (burnAmount > 0) {
|
|
IBurnVault(burnVault).burn{ value: burnAmount }();
|
|
totalBurned += burnAmount;
|
|
burnedBy[msg.sender] += burnAmount;
|
|
emit Burned(msg.sender, burnAmount, totalBurned);
|
|
}
|
|
|
|
// Distribute remainder back to validator.
|
|
if (distributeAmount > 0) {
|
|
(bool ok, ) = validator.call{ value: distributeAmount }("");
|
|
require(ok, "Splitter: distribute failed");
|
|
totalDistributed += distributeAmount;
|
|
distributedTo[validator] += distributeAmount;
|
|
emit Distributed(validator, distributeAmount, totalDistributed);
|
|
}
|
|
}
|
|
|
|
/// Convenience: validator calls this with no validator argument — distributes back to msg.sender.
|
|
function splitToSelf() external payable nonReentrant {
|
|
require(msg.value > 0, "Splitter: zero amount");
|
|
uint256 burnAmount = (msg.value * burnBps) / BPS;
|
|
uint256 distributeAmount = msg.value - burnAmount;
|
|
|
|
if (burnAmount > 0) {
|
|
IBurnVault(burnVault).burn{ value: burnAmount }();
|
|
totalBurned += burnAmount;
|
|
burnedBy[msg.sender] += burnAmount;
|
|
emit Burned(msg.sender, burnAmount, totalBurned);
|
|
}
|
|
if (distributeAmount > 0) {
|
|
(bool ok, ) = payable(msg.sender).call{ value: distributeAmount }("");
|
|
require(ok, "Splitter: distribute failed");
|
|
totalDistributed += distributeAmount;
|
|
distributedTo[msg.sender] += distributeAmount;
|
|
emit Distributed(msg.sender, distributeAmount, totalDistributed);
|
|
}
|
|
}
|
|
|
|
/// Read the live cumulative burn statistics for /network-status display.
|
|
function burnStats() external view returns (
|
|
uint256 lifetimeBurned,
|
|
uint256 lifetimeDistributed,
|
|
uint256 currentBurnBps
|
|
) {
|
|
return (totalBurned, totalDistributed, burnBps);
|
|
}
|
|
}
|