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.
281 lines
12 KiB
Solidity
281 lines
12 KiB
Solidity
// SPDX-License-Identifier: MIT
|
||
pragma solidity 0.8.23;
|
||
|
||
/**
|
||
* @title AereBestExReceipt — MiCA Article 78 best-execution receipts on-chain
|
||
* @notice EU MiCA Title V (CASP conduct) Article 78 requires every Crypto
|
||
* Asset Service Provider to demonstrate "best execution" — that the
|
||
* price, costs, speed, likelihood, size, nature and other relevant
|
||
* considerations led to the most favourable result for the client.
|
||
* Until now, this proof has lived in CASPs' private back-office logs.
|
||
* AERE turns it into an open, append-only on-chain record.
|
||
*
|
||
* For every trade executed on behalf of an EU client, a registered
|
||
* CASP MUST mint a receipt here. The receipt commits to:
|
||
*
|
||
* - which venue was used (executedVenue)
|
||
* - the price and quantity actually filled
|
||
* - the alternative venues considered, with their best quotes
|
||
* at the time of decision (alternativesRoot — Merkle root)
|
||
* - the speed (millisecond timestamp at which the order was
|
||
* accepted vs the execution timestamp)
|
||
* - the protocol/SOR algorithm version that made the routing call
|
||
*
|
||
* Anyone — clients, supervisors, journalists — can later verify by
|
||
* (a) reading the on-chain receipt and (b) reproducing the
|
||
* alternative-quote Merkle tree from the CASP's published snapshot.
|
||
*
|
||
* @dev DESIGN CHOICES:
|
||
*
|
||
* 1. NOT an NFT. ERC-721 carries semantics (transfer, marketplace)
|
||
* that don't fit a regulatory artefact. Receipts are bound to
|
||
* the CASP that issued them; transfer/ownership is irrelevant.
|
||
* Each receipt is a struct in a sealed mapping.
|
||
*
|
||
* 2. CASP registry is owned by the Foundation but registration is
|
||
* by-reference: Foundation publishes the CASP's MiCA licence
|
||
* number + national competent authority + LEI. The Foundation
|
||
* CANNOT amend a receipt after issuance, only register new
|
||
* CASPs and pause-register a bad actor.
|
||
*
|
||
* 3. No self-policing of receipt content. The contract does NOT
|
||
* attempt to validate that the venue chosen actually was the
|
||
* best — only that the CASP COMMITTED to a specific decision
|
||
* with a specific information set. Off-chain auditors / clients
|
||
* / supervisors detect non-compliance by reading the open
|
||
* record. This is the same model as Chainlink: the oracle
|
||
* commits, the market detects mispricing.
|
||
*
|
||
* 4. Per-CASP rolling counter (receiptNonce[casp]) avoids global
|
||
* counter contention. Each CASP can issue receipts in parallel
|
||
* without coordinating.
|
||
*
|
||
* 5. Pause is at the *issuance* level. A paused CASP cannot mint
|
||
* new receipts but historical receipts remain queryable and
|
||
* verifiable forever. Compliance discipline, not data deletion.
|
||
*
|
||
* @dev GAS BUDGET (target):
|
||
* - issueReceipt: ~95-105k gas (mostly cold storage SSTOREs).
|
||
* - At 0.5 gwei base × 100k gas = 50_000 gwei = 0.00005 AERE
|
||
* (~ tiny fraction of a cent at any plausible AERE price).
|
||
* - A CASP doing 10M trades/yr pays << €500/yr in receipt gas.
|
||
* - That cost is the chain's competitive moat versus Polygon /
|
||
* Arbitrum / Base — at MiCA scale it stays a rounding error.
|
||
*/
|
||
contract AereBestExReceipt {
|
||
|
||
/* ================================ types ================================ */
|
||
|
||
/// @notice Permitted classification for the receipt (matches MiCA Art 78).
|
||
enum OrderType {
|
||
Market, // 0
|
||
Limit, // 1
|
||
StopLoss, // 2
|
||
StopLimit, // 3
|
||
TWAP, // 4
|
||
VWAP, // 5
|
||
RFQ, // 6
|
||
Other // 7
|
||
}
|
||
|
||
/// @notice Permitted client classification under MiCA / MiFID II.
|
||
enum ClientClass {
|
||
Retail, // 0 — MiCA Title V conduct rules fully apply
|
||
Professional, // 1 — opt-out clients
|
||
Eligible // 2 — eligible counterparties
|
||
}
|
||
|
||
struct CaspRegistration {
|
||
bytes32 licenceRef; // hash of (MiCA-licence-number || NCA-code || LEI)
|
||
uint64 registeredAt; // unix seconds
|
||
bool paused; // emergency stop on issuance
|
||
bool exists; // set true on registration; never unset
|
||
}
|
||
|
||
struct Receipt {
|
||
// identity
|
||
address casp; // issuing CASP (== msg.sender at mint)
|
||
bytes32 clientHash; // keccak256(client-id || casp-internal-salt) — pseudonymous, MiCA Art 78(2)
|
||
ClientClass clientClass;
|
||
|
||
// order
|
||
bytes32 orderId; // CASP's internal order id (opaque); used by client to find this record
|
||
OrderType orderType;
|
||
bytes32 instrumentSymbol; // e.g. keccak256("BTC/EUR") — short stable identifier
|
||
uint128 requestedQty; // base asset, scaled to 18 dec
|
||
uint128 executedQty; // base asset, scaled to 18 dec
|
||
uint128 executedPrice; // quote asset per base, scaled to 18 dec
|
||
|
||
// routing decision
|
||
bytes32 executedVenue; // keccak256(venue identifier — e.g. "Binance.com", "Coinbase Exchange")
|
||
bytes32 alternativesRoot; // Merkle root of (venue, best-bid, best-ask, depth, timestamp) tuples
|
||
// considered at routing time; CASP publishes the leaves off-chain.
|
||
bytes32 algorithmRef; // hash of (SOR algo name || version || commit-sha) — pins which logic chose
|
||
uint64 decisionTsMs; // ms timestamp at which routing decision was made
|
||
uint64 executionTsMs; // ms timestamp at which fill confirmed
|
||
|
||
// metadata pointer
|
||
string evidenceUri; // ipfs://… or https://… — full alternatives JSON, fee schedule, etc.
|
||
// referenced read-only; chain stores only the root.
|
||
}
|
||
|
||
/* =============================== storage =============================== */
|
||
|
||
address public immutable FOUNDATION;
|
||
|
||
/// @notice CASP address → registration metadata. Once `exists`, never unset.
|
||
mapping(address => CaspRegistration) public caspOf;
|
||
|
||
/// @notice CASP → next nonce. receiptId = keccak256(casp, nonce).
|
||
mapping(address => uint256) public receiptNonce;
|
||
|
||
/// @notice global receiptId → Receipt. Append-only; never edited or deleted.
|
||
mapping(bytes32 => Receipt) internal _receipts;
|
||
|
||
/// @notice CASP → cumulative receipts issued.
|
||
mapping(address => uint256) public issuedCount;
|
||
|
||
/* ================================ events ================================ */
|
||
|
||
event CaspRegistered(address indexed casp, bytes32 licenceRef);
|
||
event CaspPaused(address indexed casp, bool paused);
|
||
event ReceiptIssued(
|
||
address indexed casp,
|
||
bytes32 indexed receiptId,
|
||
bytes32 indexed clientHash,
|
||
bytes32 instrumentSymbol,
|
||
bytes32 executedVenue,
|
||
bytes32 alternativesRoot,
|
||
uint128 executedQty,
|
||
uint128 executedPrice,
|
||
uint64 decisionTsMs,
|
||
uint64 executionTsMs,
|
||
bytes32 algorithmRef,
|
||
string evidenceUri
|
||
);
|
||
|
||
/* ================================ errors ================================ */
|
||
|
||
error NotFoundation();
|
||
error CaspExists();
|
||
error CaspUnknown();
|
||
error CaspIsPaused();
|
||
error ReceiptExists();
|
||
error ZeroLicence();
|
||
error BadTimestamps();
|
||
error BadQuantities();
|
||
error BadAlternatives();
|
||
|
||
/* =============================== modifiers ============================== */
|
||
|
||
modifier onlyFoundation() {
|
||
if (msg.sender != FOUNDATION) revert NotFoundation();
|
||
_;
|
||
}
|
||
|
||
/* ============================== constructor ============================= */
|
||
|
||
constructor(address foundation) {
|
||
require(foundation != address(0), "zero-foundation");
|
||
FOUNDATION = foundation;
|
||
}
|
||
|
||
/* ============================= registry ops ============================= */
|
||
|
||
/// @notice Foundation registers a new CASP. licenceRef MUST hash an
|
||
/// actual MiCA licence + NCA + LEI off-chain (no on-chain PII).
|
||
function registerCasp(address casp, bytes32 licenceRef) external onlyFoundation {
|
||
if (casp == address(0)) revert CaspUnknown();
|
||
if (licenceRef == bytes32(0)) revert ZeroLicence();
|
||
if (caspOf[casp].exists) revert CaspExists();
|
||
|
||
caspOf[casp] = CaspRegistration({
|
||
licenceRef: licenceRef,
|
||
registeredAt: uint64(block.timestamp),
|
||
paused: false,
|
||
exists: true
|
||
});
|
||
emit CaspRegistered(casp, licenceRef);
|
||
}
|
||
|
||
/// @notice Pause / unpause issuance for a CASP. Historical receipts
|
||
/// remain queryable and verifiable.
|
||
function setCaspPaused(address casp, bool paused) external onlyFoundation {
|
||
CaspRegistration storage r = caspOf[casp];
|
||
if (!r.exists) revert CaspUnknown();
|
||
r.paused = paused;
|
||
emit CaspPaused(casp, paused);
|
||
}
|
||
|
||
/* ============================ issuance path ============================ */
|
||
|
||
/**
|
||
* @notice Mint a new best-execution receipt. msg.sender MUST be a
|
||
* registered, non-paused CASP. The receipt is bound to this
|
||
* CASP forever and cannot be amended.
|
||
* @return receiptId = keccak256(abi.encode(msg.sender, nonce)).
|
||
*/
|
||
function issueReceipt(Receipt calldata r) external returns (bytes32 receiptId) {
|
||
CaspRegistration storage reg = caspOf[msg.sender];
|
||
if (!reg.exists) revert CaspUnknown();
|
||
if (reg.paused) revert CaspIsPaused();
|
||
|
||
if (r.casp != msg.sender) revert CaspUnknown();
|
||
if (r.executionTsMs < r.decisionTsMs) revert BadTimestamps();
|
||
if (r.executedQty == 0 || r.executedPrice == 0) revert BadQuantities();
|
||
if (r.executedQty > r.requestedQty) revert BadQuantities();
|
||
if (r.alternativesRoot == bytes32(0)) revert BadAlternatives();
|
||
if (r.executedVenue == bytes32(0)) revert BadAlternatives();
|
||
|
||
uint256 nonce = receiptNonce[msg.sender]++;
|
||
receiptId = keccak256(abi.encode(msg.sender, nonce));
|
||
if (_receipts[receiptId].casp != address(0)) revert ReceiptExists();
|
||
|
||
_receipts[receiptId] = r;
|
||
issuedCount[msg.sender] = nonce + 1;
|
||
|
||
emit ReceiptIssued(
|
||
msg.sender,
|
||
receiptId,
|
||
r.clientHash,
|
||
r.instrumentSymbol,
|
||
r.executedVenue,
|
||
r.alternativesRoot,
|
||
r.executedQty,
|
||
r.executedPrice,
|
||
r.decisionTsMs,
|
||
r.executionTsMs,
|
||
r.algorithmRef,
|
||
r.evidenceUri
|
||
);
|
||
}
|
||
|
||
/* ================================ views ================================ */
|
||
|
||
/// @notice Read a receipt by its id. Reverts not applied — caller checks
|
||
/// `r.casp == address(0)` for not-found.
|
||
function getReceipt(bytes32 receiptId) external view returns (Receipt memory r) {
|
||
return _receipts[receiptId];
|
||
}
|
||
|
||
/// @notice Deterministic receipt id for the next mint by `casp`.
|
||
/// Useful for relayer dry-runs.
|
||
function nextReceiptId(address casp) external view returns (bytes32) {
|
||
return keccak256(abi.encode(casp, receiptNonce[casp]));
|
||
}
|
||
|
||
/// @notice The decision-to-execution latency in milliseconds. One of the
|
||
/// "speed" inputs MiCA Art 78 names. Stored derivable for indexers.
|
||
function executionLatencyMs(bytes32 receiptId) external view returns (uint64) {
|
||
Receipt storage r = _receipts[receiptId];
|
||
return r.executionTsMs - r.decisionTsMs;
|
||
}
|
||
|
||
/// @notice CASP is registered, non-paused, and has issued ≥ 1 receipt.
|
||
/// The minimum filter a client should apply when choosing a CASP.
|
||
function caspIsActive(address casp) external view returns (bool) {
|
||
CaspRegistration storage reg = caspOf[casp];
|
||
return reg.exists && !reg.paused && issuedCount[casp] > 0;
|
||
}
|
||
}
|