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.
115 lines
4.6 KiB
Solidity
115 lines
4.6 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {AereUSDC} from "./AereUSDC.sol";
|
|
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
|
|
|
/// @title AereBridge — Foundation-attested on-ramp from Ethereum USDC to AereUSDC
|
|
/// @notice The off-chain bridge watcher (Foundation-operated, source under
|
|
/// /opt/aere-bridge-watcher) monitors a Foundation receiver address on
|
|
/// Ethereum mainnet for incoming USDC. When a deposit is detected and
|
|
/// confirmed (≥12 block confirmations), the watcher calls
|
|
/// `attestDeposit(ethTxHash, ethSender, aereDestination, amount)` on this
|
|
/// contract, which mints AereUSDC to the destination.
|
|
///
|
|
/// Replay protection: every (ethTxHash, logIndex) is recorded — re-attesting
|
|
/// the same deposit is a no-op.
|
|
///
|
|
/// Honest framing: this contract assumes a trusted ATTESTOR role. Phase 1
|
|
/// the attestor is a Foundation-controlled EOA. Phase 2 swaps to a multisig.
|
|
/// Phase 3 replaces with trustless settlement via Across/Hyperlane.
|
|
contract AereBridge is AccessControl {
|
|
bytes32 public constant ATTESTOR_ROLE = keccak256("ATTESTOR_ROLE");
|
|
|
|
AereUSDC public immutable aereUSDC;
|
|
|
|
/// @notice Foundation's USDC receiver address on Ethereum mainnet — informational
|
|
/// only; the watcher actually decides which Ethereum address to monitor.
|
|
string public ethReceiverHint;
|
|
|
|
/// @notice (ethTxHash, logIndex) tuple → already-attested marker. Prevents double-mint.
|
|
mapping(bytes32 => bool) public attestedDeposits;
|
|
|
|
/// @notice Total bridged in (USDC, 6 decimals). Visible for transparency dashboard.
|
|
uint256 public totalBridgedIn;
|
|
|
|
struct Deposit {
|
|
bytes32 ethTxHash;
|
|
uint64 ethLogIndex;
|
|
address ethSender;
|
|
address aereDestination;
|
|
uint256 amount;
|
|
uint64 attestedAt;
|
|
uint64 attestedBlock;
|
|
}
|
|
|
|
/// @notice Append-only deposit log. Phase 1 only; off-chain indexer reads events.
|
|
Deposit[] public deposits;
|
|
|
|
event Attested(
|
|
uint256 indexed depositId,
|
|
bytes32 indexed ethTxHash,
|
|
address indexed aereDestination,
|
|
address ethSender,
|
|
uint256 amount
|
|
);
|
|
event EthReceiverHintUpdated(string ethReceiver);
|
|
|
|
error AlreadyAttested();
|
|
error InvalidAmount();
|
|
error InvalidDestination();
|
|
|
|
constructor(address foundation, address _aereUSDC, address initialAttestor, string memory _ethReceiverHint) {
|
|
require(_aereUSDC != address(0), "bridge: aereUSDC=0");
|
|
aereUSDC = AereUSDC(_aereUSDC);
|
|
ethReceiverHint = _ethReceiverHint;
|
|
_grantRole(DEFAULT_ADMIN_ROLE, foundation);
|
|
_grantRole(ATTESTOR_ROLE, initialAttestor);
|
|
}
|
|
|
|
/// @notice Off-chain watcher calls this when an Ethereum USDC deposit is confirmed.
|
|
/// @param ethTxHash The Ethereum transaction hash containing the USDC Transfer.
|
|
/// @param ethLogIndex The log index within that tx (multiple logs per tx possible).
|
|
/// @param ethSender The Ethereum address that sent the USDC (used for refund routing).
|
|
/// @param aereDestination The AERE address to receive AereUSDC.
|
|
/// @param amount The amount in USDC units (6 decimals).
|
|
function attestDeposit(
|
|
bytes32 ethTxHash,
|
|
uint64 ethLogIndex,
|
|
address ethSender,
|
|
address aereDestination,
|
|
uint256 amount
|
|
) external onlyRole(ATTESTOR_ROLE) {
|
|
if (amount == 0) revert InvalidAmount();
|
|
if (aereDestination == address(0)) revert InvalidDestination();
|
|
bytes32 key = keccak256(abi.encodePacked(ethTxHash, ethLogIndex));
|
|
if (attestedDeposits[key]) revert AlreadyAttested();
|
|
attestedDeposits[key] = true;
|
|
|
|
uint256 id = deposits.length;
|
|
deposits.push(Deposit({
|
|
ethTxHash: ethTxHash,
|
|
ethLogIndex: ethLogIndex,
|
|
ethSender: ethSender,
|
|
aereDestination: aereDestination,
|
|
amount: amount,
|
|
attestedAt: uint64(block.timestamp),
|
|
attestedBlock: uint64(block.number)
|
|
}));
|
|
totalBridgedIn += amount;
|
|
|
|
aereUSDC.mint(aereDestination, amount);
|
|
emit Attested(id, ethTxHash, aereDestination, ethSender, amount);
|
|
}
|
|
|
|
/// @notice Foundation can update the receiver-address hint shown to users.
|
|
function setEthReceiverHint(string calldata _ethReceiverHint) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
ethReceiverHint = _ethReceiverHint;
|
|
emit EthReceiverHintUpdated(_ethReceiverHint);
|
|
}
|
|
|
|
function depositCount() external view returns (uint256) {
|
|
return deposits.length;
|
|
}
|
|
}
|