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.
226 lines
8.6 KiB
Solidity
226 lines
8.6 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "./AereRollupSettlementV2.sol";
|
|
|
|
/**
|
|
* @title AereRaaSFactoryV2 — permissionless Rollup-as-a-Service registry (bug-fixed template)
|
|
* @notice Bug-fix redeploy of AereRaaSFactory (0x8C1b0018ab8C4299a75621a4BdD3cF26971B26Cd,
|
|
* 0 rollups registered). Identical factory model — permissionless
|
|
* registration, refundable bond, MIN_SINK_BPS floor, no admin pause /
|
|
* slash / upgrade — with ONE change: it deploys AereRollupSettlementV2
|
|
* as the per-rollup settlement template instead of the buggy V1.
|
|
*
|
|
* AereRollupSettlementV2 fixes F6 (last-block challenge grief),
|
|
* F7 (rejected-epoch finalisation freeze), the isFinalised ancestor
|
|
* medium, and the settleRevenue stuck-residual low. See that contract.
|
|
*
|
|
* New immutable DEFAULT_DEFENSE_GRACE is threaded into every settlement
|
|
* the factory deploys (per-registration override allowed, 0 → default),
|
|
* mirroring DEFAULT_CHALLENGE_WINDOW.
|
|
*
|
|
* IMMUTABILITY at factory deploy: BOND_TOKEN, SINK, REGISTRATION_BOND,
|
|
* MIN_SINK_BPS, DEFAULT_CHALLENGE_WINDOW, DEFAULT_DEFENSE_GRACE,
|
|
* FOUNDATION. The factory cannot retroactively change a registered
|
|
* rollup's parameters.
|
|
*/
|
|
contract AereRaaSFactoryV2 is ReentrancyGuard {
|
|
|
|
/* ------------------------------- immutable ------------------------------- */
|
|
|
|
address public immutable BOND_TOKEN;
|
|
address public immutable SINK;
|
|
uint256 public immutable REGISTRATION_BOND;
|
|
uint16 public immutable MIN_SINK_BPS;
|
|
uint64 public immutable DEFAULT_CHALLENGE_WINDOW;
|
|
uint64 public immutable DEFAULT_DEFENSE_GRACE; // V2: default sequencer defense grace
|
|
address public immutable FOUNDATION; // signs graceful-exit attestations in Phase 1
|
|
|
|
/* --------------------------------- state -------------------------------- */
|
|
|
|
struct Rollup {
|
|
bool registered;
|
|
bool exited;
|
|
address settlement;
|
|
address rollupOwner;
|
|
uint256 bond;
|
|
uint64 registeredAt;
|
|
string metadataUri;
|
|
}
|
|
|
|
/// @notice rollupId → Rollup
|
|
mapping(bytes32 => Rollup) public rollups;
|
|
bytes32[] public rollupIds;
|
|
|
|
/* --------------------------------- events ------------------------------- */
|
|
|
|
event RollupRegistered(
|
|
bytes32 indexed rollupId,
|
|
address indexed rollupOwner,
|
|
address indexed settlement,
|
|
uint256 bond,
|
|
string metadataUri
|
|
);
|
|
event RollupExited(bytes32 indexed rollupId, address recipient, uint256 bondReturned);
|
|
event MetadataUpdated(bytes32 indexed rollupId, string newMetadataUri);
|
|
|
|
/* --------------------------------- errors ------------------------------- */
|
|
|
|
error AlreadyRegistered();
|
|
error NotRegistered();
|
|
error AlreadyExited();
|
|
error NotOwner();
|
|
error BondMismatch();
|
|
error InvalidSplits();
|
|
error SinkBpsTooLow();
|
|
error InvalidExitSig();
|
|
error TransferFailed();
|
|
error ZeroAddress();
|
|
|
|
/* ----------------------------- constructor ------------------------------ */
|
|
|
|
constructor(
|
|
address bondToken,
|
|
address sink,
|
|
address foundation,
|
|
uint256 registrationBond,
|
|
uint16 minSinkBps,
|
|
uint64 defaultChallengeWindow,
|
|
uint64 defaultDefenseGrace
|
|
) {
|
|
if (bondToken == address(0) || sink == address(0) || foundation == address(0)) revert ZeroAddress();
|
|
BOND_TOKEN = bondToken;
|
|
SINK = sink;
|
|
FOUNDATION = foundation;
|
|
REGISTRATION_BOND = registrationBond;
|
|
MIN_SINK_BPS = minSinkBps;
|
|
DEFAULT_CHALLENGE_WINDOW = defaultChallengeWindow;
|
|
DEFAULT_DEFENSE_GRACE = defaultDefenseGrace;
|
|
}
|
|
|
|
/* ----------------------------- registration ----------------------------- */
|
|
|
|
function registerRollup(
|
|
bytes32 rollupId,
|
|
address rollupOwner,
|
|
address sequencer,
|
|
address debtToken,
|
|
uint16 sinkBps,
|
|
uint16 rollupBps,
|
|
uint16 sequencerBps,
|
|
uint256 minChallengeBond,
|
|
uint64 challengeWindow,
|
|
uint64 defenseGrace,
|
|
string calldata metadataUri
|
|
) external nonReentrant returns (address settlementAddr) {
|
|
if (rollups[rollupId].registered) revert AlreadyRegistered();
|
|
if (rollupOwner == address(0) || sequencer == address(0) || debtToken == address(0)) revert ZeroAddress();
|
|
if (uint256(sinkBps) + uint256(rollupBps) + uint256(sequencerBps) != 10_000) revert InvalidSplits();
|
|
if (sinkBps < MIN_SINK_BPS) revert SinkBpsTooLow();
|
|
if (challengeWindow == 0) challengeWindow = DEFAULT_CHALLENGE_WINDOW;
|
|
if (defenseGrace == 0) defenseGrace = DEFAULT_DEFENSE_GRACE;
|
|
|
|
// Pull bond.
|
|
if (REGISTRATION_BOND > 0) {
|
|
if (!IERC20(BOND_TOKEN).transferFrom(msg.sender, address(this), REGISTRATION_BOND)) revert TransferFailed();
|
|
}
|
|
|
|
// Deploy settlement contract (V2 template).
|
|
AereRollupSettlementV2.Config memory cfg = AereRollupSettlementV2.Config({
|
|
rollupId: rollupId,
|
|
rollupOwner: rollupOwner,
|
|
sequencer: sequencer,
|
|
sink: SINK,
|
|
debtToken: debtToken,
|
|
challengeWindow: challengeWindow,
|
|
defenseGrace: defenseGrace,
|
|
sinkBps: sinkBps,
|
|
rollupBps: rollupBps,
|
|
sequencerBps: sequencerBps,
|
|
minChallengeBond: minChallengeBond
|
|
});
|
|
AereRollupSettlementV2 s = new AereRollupSettlementV2(cfg);
|
|
settlementAddr = address(s);
|
|
|
|
rollups[rollupId] = Rollup({
|
|
registered: true,
|
|
exited: false,
|
|
settlement: settlementAddr,
|
|
rollupOwner: rollupOwner,
|
|
bond: REGISTRATION_BOND,
|
|
registeredAt: uint64(block.timestamp),
|
|
metadataUri: metadataUri
|
|
});
|
|
rollupIds.push(rollupId);
|
|
|
|
emit RollupRegistered(rollupId, rollupOwner, settlementAddr, REGISTRATION_BOND, metadataUri);
|
|
}
|
|
|
|
function updateMetadata(bytes32 rollupId, string calldata newMetadataUri) external {
|
|
Rollup storage r = rollups[rollupId];
|
|
if (!r.registered) revert NotRegistered();
|
|
if (msg.sender != r.rollupOwner) revert NotOwner();
|
|
r.metadataUri = newMetadataUri;
|
|
emit MetadataUpdated(rollupId, newMetadataUri);
|
|
}
|
|
|
|
/* -------------------------------- exit ---------------------------------- */
|
|
|
|
/// @notice Phase 1: rollupOwner submits a Foundation-signed attestation that
|
|
/// the rollup has gracefully exited. Factory verifies signature and
|
|
/// returns the bond. (Unchanged from V1.)
|
|
function exitRollup(
|
|
bytes32 rollupId,
|
|
address recipient,
|
|
uint64 attestationDeadline,
|
|
bytes calldata foundationSig
|
|
) external nonReentrant {
|
|
Rollup storage r = rollups[rollupId];
|
|
if (!r.registered) revert NotRegistered();
|
|
if (r.exited) revert AlreadyExited();
|
|
if (msg.sender != r.rollupOwner) revert NotOwner();
|
|
if (recipient == address(0)) revert ZeroAddress();
|
|
if (block.timestamp > attestationDeadline) revert InvalidExitSig();
|
|
|
|
bytes32 hash = keccak256(abi.encode(
|
|
"AereRaaSFactory:exit",
|
|
block.chainid,
|
|
address(this),
|
|
rollupId,
|
|
recipient,
|
|
attestationDeadline
|
|
));
|
|
bytes32 ethHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
|
|
address recovered = _recover(ethHash, foundationSig);
|
|
if (recovered == address(0) || recovered != FOUNDATION) revert InvalidExitSig();
|
|
|
|
uint256 bond = r.bond;
|
|
r.bond = 0;
|
|
r.exited = true;
|
|
if (bond > 0) {
|
|
if (!IERC20(BOND_TOKEN).transfer(recipient, bond)) revert TransferFailed();
|
|
}
|
|
emit RollupExited(rollupId, recipient, bond);
|
|
}
|
|
|
|
/* ---------------------------------- views ------------------------------- */
|
|
|
|
function rollupCount() external view returns (uint256) { return rollupIds.length; }
|
|
|
|
/* ------------------------------- internal ------------------------------- */
|
|
|
|
function _recover(bytes32 hash, bytes calldata sig) internal pure returns (address) {
|
|
if (sig.length != 65) return address(0);
|
|
bytes32 r; bytes32 s; uint8 v;
|
|
assembly {
|
|
r := calldataload(sig.offset)
|
|
s := calldataload(add(sig.offset, 32))
|
|
v := byte(0, calldataload(add(sig.offset, 64)))
|
|
}
|
|
if (v < 27) v += 27;
|
|
return ecrecover(hash, v, r, s);
|
|
}
|
|
}
|