Advanced
Rollups: Optimistic vs ZK Internals — Project — minimal rollup settlement skeleton
Understand scaling choices.
Developer Advanced
7/8 — Rollups: Optimistic vs ZK Internals — Project — minimal rollup settlement skeleton
What to writeA tiny L1-side contract that escrows deposits, tracks batches, and releases withdrawals only after proof/timeout conditions.
- State
Escrow token, batch structs, proof flags, challenge window, DA flag, and pause. - Deposit
Lock tokens, emit Deposit event with batch ref. - Publish batch
Record commitment root + timestamp; toggle mode (optimistic or validity) for exits. - Prove / timeout
In optimistic mode, allow proof submission during window; else allow exit after window. In validity mode, require proofVerified. - Withdraw
Verify inclusion (placeholder) and proof/timer/DA flags before release. - Admin safety
Pause, update challenge window, and set DA availability.
IERC20 public immutable token;
uint256 public challengeWindow = 7 days;
bool public validityMode; // false = optimistic, true = validity
bool public dataAvailable = true;
bool public paused;
struct Batch { bytes32 root; uint64 timestamp; bool proofVerified; }
mapping(uint256 => Batch) public batches;
uint256 public nextBatchId;
event Deposit(address indexed user, uint256 amount, uint256 batchId);
event BatchPublished(uint256 indexed batchId, bytes32 root, bool validityMode);
event ProofVerified(uint256 indexed batchId);
event Withdrawn(address indexed user, uint256 amount, uint256 batchId);
event DataAvailability(bool available);
Keeps L1 escrow, batches, and mode flags minimal.
function deposit(uint256 amount) external {
require(!paused, "paused");
token.transferFrom(msg.sender, address(this), amount);
emit Deposit(msg.sender, amount, nextBatchId);
}
function publishBatch(bytes32 root, bool _validityMode) external {
require(!paused, "paused");
batches[nextBatchId] = Batch({ root: root, timestamp: uint64(block.timestamp), proofVerified: false });
validityMode = _validityMode;
emit BatchPublished(nextBatchId, root, validityMode);
nextBatchId++;
}
function verifyProof(uint256 batchId) external {
batches[batchId].proofVerified = true;
emit ProofVerified(batchId);
}
function withdraw(uint256 batchId, address to, uint256 amount, bytes calldata proof) external {
require(!paused, "paused");
require(dataAvailable, "DA");
Batch memory b = batches[batchId];
if (validityMode) {
require(b.proofVerified, "need proof");
} else {
require(block.timestamp >= b.timestamp + challengeWindow, "challenge");
}
// TODO: verify inclusion proof against b.root
token.transfer(to, amount);
emit Withdrawn(to, amount, batchId);
}
Models both optimistic (time + fraud proof) and validity (proof gating) flows with a DA toggle.
function setChallengeWindow(uint256 w) external { challengeWindow = w; }
function setDataAvailability(bool available) external { dataAvailable = available; emit DataAvailability(available); }
function setPaused(bool p) external { paused = p; }
Try itTests: exits blocked before timer/proof; fraud proof prevents exit; DA flag halts exits; pause blocks deposit/withdraw; switch validityMode and require proof.