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.
214 lines
8.3 KiB
Solidity
214 lines
8.3 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
|
/**
|
|
* @title AereForensicEventRegistry — Forta-style detection bot output
|
|
* registry for Chain 2800
|
|
* @notice Open registry where third-party detection bots (mirror of the
|
|
* Forta Network ecosystem) post structured alerts about
|
|
* suspicious on-chain activity. Bots register first via
|
|
* registerBot(); they then submit alert events tagged with a
|
|
* severity and an alert type. Consumer dApps (lending markets,
|
|
* settlement venues) query the registry to gate counterparty
|
|
* actions when a high-severity recent alert flags an address.
|
|
*
|
|
* AERE Foundation operates a small initial bot fleet (anomalous-
|
|
* flow detection, sandwich-tx detection, sanctioned-flow
|
|
* detection); third-party operators are welcomed via
|
|
* permissionless self-registration.
|
|
*
|
|
* No asset custody. No transaction interception. Read-only audit
|
|
* surface.
|
|
*
|
|
* @dev LISTING + RANKING:
|
|
* - Each bot has an immutable owner-set stakeRequirement (default
|
|
* 0 in Phase 1 — purely reputational). Phase 2 (Q2 2027) will
|
|
* require an AERE bond + Foundation gating for high-severity
|
|
* alerts.
|
|
* - Per-bot reputation accrues from confirmed alerts (Foundation
|
|
* confirms in Phase 1; community-staked in Phase 2).
|
|
*
|
|
* ALERT TAXONOMY (initial; bots can extend):
|
|
* 0 = unknown / catch-all
|
|
* 1 = sanctioned-flow detected (counterparty matches Sanctions
|
|
* Registry)
|
|
* 2 = sandwich-tx / MEV pattern
|
|
* 3 = anomalous-flow (sudden TVL drain, unusual contract)
|
|
* 4 = oracle deviation
|
|
* 5 = bridge exploit pattern
|
|
* 6 = phishing contract pattern
|
|
* 7 = governance vote-buying pattern
|
|
* 8 = honeypot pattern
|
|
* 9 = rug-pull pattern
|
|
*
|
|
* SEVERITY:
|
|
* 1 = info
|
|
* 2 = low
|
|
* 3 = medium
|
|
* 4 = high
|
|
* 5 = critical
|
|
*/
|
|
contract AereForensicEventRegistry is Ownable {
|
|
|
|
struct Bot {
|
|
bool registered;
|
|
bool active;
|
|
address owner;
|
|
string name;
|
|
string metadataUri; // ipfs / https pointer to bot metadata
|
|
uint256 stakeRequirement;
|
|
uint256 alertCount;
|
|
uint256 confirmedCount;
|
|
}
|
|
|
|
struct Alert {
|
|
uint256 botId;
|
|
address subject; // the address the alert is about
|
|
uint8 alertType;
|
|
uint8 severity;
|
|
uint64 timestamp;
|
|
string detailUri; // off-chain pointer (e.g. ipfs) with full details
|
|
bool confirmed; // Foundation confirmation in Phase 1
|
|
}
|
|
|
|
/// @notice botId → Bot
|
|
mapping(uint256 => Bot) public bots;
|
|
uint256 public nextBotId;
|
|
|
|
/// @notice alertId → Alert
|
|
mapping(uint256 => Alert) public alerts;
|
|
uint256 public nextAlertId;
|
|
|
|
/// @notice subject → recent alert ids (most recent first; capped at
|
|
/// RECENT_ALERTS_CAP per address by the off-chain indexer).
|
|
mapping(address => uint256[]) public subjectAlerts;
|
|
|
|
/* --------------------------------- events ------------------------------- */
|
|
|
|
event BotRegistered(uint256 indexed botId, address indexed owner, string name, string metadataUri);
|
|
event BotUpdated(uint256 indexed botId, string newMetadataUri, bool active);
|
|
event AlertEmitted(uint256 indexed alertId, uint256 indexed botId, address indexed subject, uint8 alertType, uint8 severity, string detailUri);
|
|
event AlertConfirmed(uint256 indexed alertId, uint256 indexed botId);
|
|
event AlertRejected(uint256 indexed alertId, uint256 indexed botId, string reason);
|
|
|
|
/* --------------------------------- errors ------------------------------- */
|
|
|
|
error NotBotOwner();
|
|
error UnknownBot();
|
|
error UnknownAlert();
|
|
error BotInactive();
|
|
error ZeroSubject();
|
|
|
|
/* ----------------------------- bot registration ------------------------- */
|
|
|
|
function registerBot(string calldata name, string calldata metadataUri) external returns (uint256 botId) {
|
|
botId = nextBotId++;
|
|
bots[botId] = Bot({
|
|
registered: true,
|
|
active: true,
|
|
owner: msg.sender,
|
|
name: name,
|
|
metadataUri: metadataUri,
|
|
stakeRequirement: 0,
|
|
alertCount: 0,
|
|
confirmedCount: 0
|
|
});
|
|
emit BotRegistered(botId, msg.sender, name, metadataUri);
|
|
}
|
|
|
|
function updateBot(uint256 botId, string calldata newMetadataUri, bool active) external {
|
|
Bot storage b = bots[botId];
|
|
if (!b.registered) revert UnknownBot();
|
|
if (msg.sender != b.owner) revert NotBotOwner();
|
|
b.metadataUri = newMetadataUri;
|
|
b.active = active;
|
|
emit BotUpdated(botId, newMetadataUri, active);
|
|
}
|
|
|
|
/* ----------------------------- alert emission --------------------------- */
|
|
|
|
function emitAlert(
|
|
uint256 botId,
|
|
address subject,
|
|
uint8 alertType,
|
|
uint8 severity,
|
|
string calldata detailUri
|
|
) external returns (uint256 alertId) {
|
|
Bot storage b = bots[botId];
|
|
if (!b.registered) revert UnknownBot();
|
|
if (!b.active) revert BotInactive();
|
|
if (msg.sender != b.owner) revert NotBotOwner();
|
|
if (subject == address(0)) revert ZeroSubject();
|
|
|
|
alertId = nextAlertId++;
|
|
alerts[alertId] = Alert({
|
|
botId: botId,
|
|
subject: subject,
|
|
alertType: alertType,
|
|
severity: severity,
|
|
timestamp: uint64(block.timestamp),
|
|
detailUri: detailUri,
|
|
confirmed: false
|
|
});
|
|
subjectAlerts[subject].push(alertId);
|
|
b.alertCount++;
|
|
emit AlertEmitted(alertId, botId, subject, alertType, severity, detailUri);
|
|
}
|
|
|
|
/* ------------------------------- confirmation --------------------------- */
|
|
|
|
/// @notice Phase 1: Foundation reviews + confirms valid alerts. Phase 2
|
|
/// replaces with staked-community confirmation.
|
|
function confirmAlert(uint256 alertId) external onlyOwner {
|
|
Alert storage a = alerts[alertId];
|
|
if (a.timestamp == 0) revert UnknownAlert();
|
|
if (a.confirmed) return;
|
|
a.confirmed = true;
|
|
bots[a.botId].confirmedCount++;
|
|
emit AlertConfirmed(alertId, a.botId);
|
|
}
|
|
|
|
function rejectAlert(uint256 alertId, string calldata reason) external onlyOwner {
|
|
Alert storage a = alerts[alertId];
|
|
if (a.timestamp == 0) revert UnknownAlert();
|
|
emit AlertRejected(alertId, a.botId, reason);
|
|
}
|
|
|
|
/* ----------------------------------- views ------------------------------ */
|
|
|
|
/// @notice ROUND-3 FIX: cap maximum iterations to prevent griefable DoS
|
|
/// when an attacker spams a target subject with alerts. Consumers
|
|
/// (lending markets, etc.) that query this view in a transaction
|
|
/// path would otherwise be reverted by an attacker who emitted
|
|
/// millions of low-severity alerts against the subject.
|
|
uint256 public constant MAX_RECENT_ITERATIONS = 256;
|
|
|
|
/// @notice Returns the count of CONFIRMED alerts of `minSeverity` or
|
|
/// higher emitted within the last `windowSeconds` against the
|
|
/// given subject. Bounded at MAX_RECENT_ITERATIONS — for subjects
|
|
/// with > 256 recent alerts, only the most-recent 256 are
|
|
/// counted (still a useful signal; spam pushes older legit
|
|
/// alerts out of the window).
|
|
function recentConfirmedAlertCount(
|
|
address subject,
|
|
uint8 minSeverity,
|
|
uint256 windowSeconds
|
|
) external view returns (uint256 count) {
|
|
uint256[] storage ids = subjectAlerts[subject];
|
|
uint256 cutoff = block.timestamp > windowSeconds ? block.timestamp - windowSeconds : 0;
|
|
uint256 iter = 0;
|
|
for (uint256 i = ids.length; i > 0 && iter < MAX_RECENT_ITERATIONS; i--) {
|
|
Alert storage a = alerts[ids[i - 1]];
|
|
if (a.timestamp < cutoff) break;
|
|
if (a.confirmed && a.severity >= minSeverity) count++;
|
|
unchecked { iter++; }
|
|
}
|
|
}
|
|
|
|
function subjectAlertCount(address subject) external view returns (uint256) {
|
|
return subjectAlerts[subject].length;
|
|
}
|
|
}
|