aere-contracts/contracts/AereCoinbaseSplitter.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

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);
}
}