L2, Oracle & Cross-chain Systems
This chapter is about what happens when the system does not progress normally: sequencer downtime, stale prices, duplicated messages, out-of-order delivery, delayed exits, and compromised attesters.
1. Chain profile before deployment
Write a profile per chain: chain ID, canonical bridge, L1/L2 finality policy, reorg depth, sequencer assumptions, withdrawal path, gas/DA fees, supported opcodes/precompiles, RPC providers, archive/trace requirements, and emergency contacts. Polygon PoS, optimistic rollups, zk rollups, validiums, and appchains must not share one generic “EVM chain” policy.
1b. Light-client vs attestation bridges
| Approach | On-chain verification | Risk |
|---|---|---|
| Multisig / oracle network | Signature threshold | Key compromise, collusion |
| Light client | Header + proof verification | Implementation bugs, gas cost |
| ZK proof of state | Verifier contract | Circuit/verifier governance |
Your lab receiver (labs/phase-23-bridge/) models attestation-style delivery—document which trust model your production integration actually uses.
2. Oracle adapter with a policy boundary
struct PricePolicy { uint32 maxAge; uint8 feedDecimals; uint16 maxDeviationBps; }
function readPrice() external view returns (uint256) {
(, int256 answer,, uint256 updatedAt,) = primary.latestRoundData();
if (answer <= 0 || block.timestamp - updatedAt > policy.maxAge) revert Stale();
uint256 normalized = normalize(uint256(answer), policy.feedDecimals);
if (deviation(normalized, referencePrice) > policy.maxDeviationBps) revert Deviation();
return normalized;
}
Document who supplies the reference, whether it is independent, what happens when feeds disagree, how a circuit breaker is reset, and how a sequencer outage is recognized. Never hide these rules in a frontend.
3. Message state machine
enum Status { None, Sent, Delivered, Executed, Failed, Cancelled }
mapping(bytes32 => Status) public status;
function receiveMessage(bytes32 id, bytes calldata payload) external {
_authenticateTransportAndRemoteSender();
if (status[id] != Status.Sent) revert InvalidStatus();
status[id] = Status.Delivered; // consume before arbitrary execution
_execute(payload);
status[id] = Status.Executed;
}
| Control | Failure it contains |
|---|---|
| Authenticated endpoint + remote peer | forged delivery |
| Message ID, source chain, nonce, consumed state | replay/duplicate processing |
| Rate limit, mint cap, circuit breaker | catastrophic loss after a bug/key compromise |
| Idempotent retry/reconciliation | timeouts and provider ambiguity |
| Full lifecycle monitoring | “sent” mistaken for “settled” |
4. Practical lab — L2-safe collateral adapter
- Deploy a collateral adapter to an L2 testnet. Include
block.chainidchecks, per-chain config, oracle staleness/decimals/positive-answer validation, and a sequencer-up policy if applicable. - Write tests for stale price, decimal mismatch, outage grace period, fee underestimation, reorg-safe event indexing, and wrong-chain wallet connection.
- Build an indexer that uses block hashes, a configurable confirmation depth per chain, rewind/replay logic, and a durable idempotency key.
- Produce an operations dashboard with indexer lag, price age, sequencer status, RPC error rate, and failed transaction/message counters.
5. Practical lab — simulated bridge receiver
Relayer ops: docs/RELAYER_RUNBOOK.md. Lab: labs/phase-23-bridge/solution/.