Security Model
🏠 Home > Technical Reference > Security Model
A comprehensive breakdown of Aurellion's on-chain security architecture — access control, fund safety, reentrancy guards, MEV protection, and known risks.
Threat Model
Aurellion must defend against:
| Threat | Where | Mitigation |
|---|---|---|
| Unauthorised minting | AssetsFacet | validNode modifier — only verified node operators |
| Reentrancy attacks | BridgeFacet, AuSysFacet, OrderRouterFacet | ReentrancyGuard on all state-changing functions |
| Double-spend / double-settlement | BridgeFacet | journeyRewardPaid flag + status checks |
| MEV front-running | CLOB matching | Commit-reveal scheme for large orders |
| Flash loan manipulation | CLOB circuit breaker | Price-change threshold trips market pause |
| Fake driver/dispatcher | AuSysFacet | DRIVER_ROLE and DISPATCHER_ROLE required |
| Node impersonation | NodesFacet, AssetsFacet | validNode flag gated by admin |
| Capacity overselling | NodesFacet | Atomic reduceCapacityForOrder with revert on insufficient capacity |
| Custody theft | AssetsFacet | CannotRedeemOwnCustody — custodian cannot self-redeem |
| Storage collision (upgrades) | DiamondStorage | Fixed storage slot + append-only struct |
| Facet upgrade abuse | DiamondCutFacet | onlyOwner guard on diamondCut |
| Operator rug (RWY) | RWYStakingFacet | Collateral requirement (20%) + cancel mechanism |
Access Control Layers
Layer 1: Diamond Ownership
LibDiamond.enforceIsContractOwner()The Diamond owner (deployer multisig on mainnet) controls:
- Adding/replacing/removing facets (
diamondCut) - Setting fee recipients
- Setting system parameters
- Approving operators
- Emergency pause
Layer 2: RBAC Roles
Stored in ausysRoles[role][address] in AppStorage:
| Role | Keccak | Who Holds It | Capabilities |
|---|---|---|---|
ADMIN_ROLE |
keccak256("ADMIN_ROLE") |
Aurellion ops team | Grant/revoke driver/dispatcher roles, manage AuSys config |
DRIVER_ROLE |
keccak256("DRIVER_ROLE") |
Verified couriers | Accept journeys, sign packages, complete deliveries |
DISPATCHER_ROLE |
keccak256("DISPATCHER_ROLE") |
Platform dispatcher | Assign drivers to journeys |
Layer 3: Node Validation
modifier validNode(address node) {
bytes32[] storage ownerNodes = s.ownerNodes[node];
bool hasActiveNode = false;
for (uint256 i = 0; i < ownerNodes.length; i++) {
if (s.nodes[ownerNodes[i]].active && s.nodes[ownerNodes[i]].validNode) {
hasActiveNode = true; break;
}
}
if (!hasActiveNode) revert InvalidNode();
_;
}Minting requires a verified node — admin must explicitly set validNode=true after off-chain verification.
Layer 4: Participant Checks
AuSysFacet validates the caller is a party to the relevant order or journey before allowing action:
if (msg.sender != journey.sender && msg.sender != journey.driver && msg.sender != journey.receiver)
revert NotJourneyParticipant();Reentrancy Protection
| Facet | Guard | Scope |
|---|---|---|
BridgeFacet |
ReentrancyGuard (OpenZeppelin) |
createUnifiedOrder, settleOrder, cancelBridgeOrder |
AuSysFacet |
ReentrancyGuard |
createOrder, handOff, settleOrder |
OrderRouterFacet |
ReentrancyGuard |
placeOrder, placeMarketOrder, cancelOrder |
CLOBCoreFacet |
ReentrancyGuard |
placeLimitOrder, cancelOrder |
CLOBMatchingFacet |
ReentrancyGuard |
matchOrder, executeMatch |
RWYStakingFacet |
Custom reentrancy (RWYStorage) | stakeToOpportunity, claimProfit |
All ERC-20 and ERC-1155 transfers use OpenZeppelin's SafeERC20.safeTransfer and the ERC-1155 callback pattern to handle malicious receiver contracts safely.
Fund Safety
Escrow Invariants
At any point, the Diamond's ERC-20 balance must be ≥ sum of all escrowed amounts:
diamond.quoteToken.balanceOf(diamond) ≥
Σ unifiedOrder.escrowedAmount for all PENDING/MATCHED/IN_LOGISTICS ordersThis is not enforced on-chain with an explicit invariant check — it is maintained by construction:
createUnifiedOrderpullstotalEscrowin before creating the recordsettleOrderandcancelBridgeOrderare the only functions that release escrow- Both are guarded by status checks preventing double-execution
Settlement Checks
// settleOrder validates:
require(order.status == UNIFIED_IN_LOGISTICS, "Wrong status");
// After settlement:
order.status = UNIFIED_SETTLED;
// Any second call reverts on the status checkJourney Reward Guard
require(!journeyRewardPaid[journeyId], "RewardAlreadyPaid");
journeyRewardPaid[journeyId] = true;
// Then transfer bounty — check-effect-interact pattern
IERC20(payToken).safeTransfer(driver, bounty);MEV Protection
Commit-Reveal Scheme
Orders above commitmentThreshold (default: 10,000e18 quote tokens) must use two-phase placement:
Phase 1 — Commit (public)
bytes32 commitment = keccak256(abi.encode(salt, orderParams));
CLOBMEVFacet.commitOrder(commitment);
// Stored: committedOrders[commitment] = { committer, blockNumber }Phase 2 — Reveal (after minRevealDelay blocks)
CLOBMEVFacet.revealOrder(salt, orderParams);
// Validates: keccak256(salt, orderParams) == commitment
// Validates: block.number >= commitBlock + minRevealDelay
// Executes the orderThis prevents front-running: bots see the commitment but can't know the order direction/price until it's too late.
Rate Limiting
Per-address per-block limits:
maxOrdersPerBlock = 100— prevents spam attacksmaxVolumePerBlock = 1,000,000e18— caps single-block volume
Circuit Breakers
Each market has a CircuitBreaker that monitors price velocity:
if |fillPrice - lastPrice| / lastPrice > priceChangeThreshold:
market.paused = true
market.pausedUntilBlock = block.number + cooldownPeriodEmits CircuitBreakerTripped and reverts the triggering transaction.
Upgrade Safety
Storage Append-Only Rule
AppStorage must only ever be appended to. Never:
- Reorder fields
- Remove fields (use tombstone booleans)
- Change field types
Violation would corrupt storage for all existing data.
Facet Replacement
Replacing a facet (logic upgrade) is safe as long as the new implementation reads/writes storage identically. New storage fields can be appended to AppStorage alongside the new facet.
Initialisation
Facets that require initialisation use Initializable from OpenZeppelin:
function initialize() public initializer {}This prevents double-initialisation which could reset state.
Known Risks & Mitigations
| Risk | Severity | Mitigation Status |
|---|---|---|
| Admin key compromise | Critical | Planned: multisig ownership on mainnet |
| Oracle-free price discovery | Medium | CLOB is self-pricing — no oracle dependency or manipulation vector |
| Large order sandwich attacks | Medium | Commit-reveal for orders > threshold |
| Node operator minting inflation | Low | Admin must validate each node; classes controlled by owner |
| Gas limit on large node arrays | Low | MAX_NODES_PER_ORDER = 20, MAX_JOURNEYS_PER_ORDER = 10 |
| Timestamp manipulation | Low | ETAs are non-binding hints; status transitions don't depend on timestamps for finality |
| ERC-1155 callback reentrancy | Low | ReentrancyGuard on all external-call functions |
| Custody griefing (sender refuses to sign) | Low | Admin can override journey status; order can be cancelled with refund |
Audit Recommendations
Before mainnet launch, the following facets require independent security audit:
- BridgeFacet — handles escrow of user funds
- CLOBMatchingFacet — complex matching logic with token transfers
- AuSysFacet — custody signature system and settlement
- RWYStakingFacet — staking and profit distribution
- DiamondStorage — storage layout correctness across all facets