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.

  1. State
    Escrow token, batch structs, proof flags, challenge window, DA flag, and pause.
  2. Deposit
    Lock tokens, emit Deposit event with batch ref.
  3. Publish batch
    Record commitment root + timestamp; toggle mode (optimistic or validity) for exits.
  4. Prove / timeout
    In optimistic mode, allow proof submission during window; else allow exit after window. In validity mode, require proofVerified.
  5. Withdraw
    Verify inclusion (placeholder) and proof/timer/DA flags before release.
  6. 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.

← Previous section
Next section →