aere-contracts/contracts/bridge2/AereBridge.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

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