Documentation, 02 of 04

Architecture

The register, the oblivious walk, and why a shard holds 32.

FHEVM enforces two limits on a single transaction. Total work is capped at 20,000,000 HCU, and the longest chain of dependent operations is capped at 5,000,000. Exceeding either reverts. The second is the one that shapes this design, because it cannot be worked around.

The obvious build encrypts balances and scans them at draw time, accumulating into one running total. Every addition depends on the one before it, so the whole scan is a single chain of ciphertext additions at 162,000 HCU each. That crosses the depth limit at 30 depositors, and no amount of gas buys past it.

Sortis keeps time-weighted stake in a segment tree and descends it. The chain then grows with the height of the tree rather than with the number of people in it, which is the whole architectural argument.

The update path is parallel, not deep

Maintaining a segment tree the textbook way recomputes each parent from its children, so every parent waits on the child written a step earlier. At height 16 that is seventeen additions in a chain, on the deposit path, on every deposit.

_update folds the direction of the change into the delta once, then adds that same single ciphertext to every node on the path. Each node now depends only on its own previous value, so the writes are independent of one another and bill against the global budget instead of the depth one. Sequential depth for a commit is 920,000 HCU and is flat in shard size: the same figure at 2^4 as at 2^16.

The walk never branches on a ciphertext

At each level the walk compares the remaining lot against the left subtree's weight, uses that encrypted comparison to select whether to subtract, and folds the branch bit into an encrypted index. Solidity never sees which way it went.

That has a consequence which is easy to miss and dominates the cost. Because the index stays encrypted, the contract cannot address the next node in plaintext either. It has to resolve each level's child obliviously, folding together every candidate that level could have reached. Touching every leaf the draw could have picked is not an inefficiency to optimise away; it is what hiding the winner means. The cost is therefore linear in stakes however the tree is arranged.

A shard holds 32, and the number is measured

   4 stakes   2,199,000 HCU   43.98%   fits
   8 stakes   3,020,000 HCU   60.40%   fits
  16 stakes   3,748,000 HCU   74.96%   fits
  32 stakes   4,476,000 HCU   89.52%   fits
  64 stakes     reverts         depth budget

The ceiling is set by what a draw costs, not by what a walk costs. drawLot reduces the lot modulo the published total before it descends, and FHE.rem is a 1,153,000 chain that the entire walk then hangs off. Measuring _walk alone says 64 stakes fit. Measuring the transaction that actually has to land says 32, at 89.52% of budget.

A shard was briefly deployed at the larger size on the strength of the walk figure alone and could not have settled its own draw. The number that sets capacity has to be the cost of the whole transaction, which is why capacity is enforced in the contract: the 33rd depositor is rejected rather than silently pushing a draw past what it can settle.

Checked against the coprocessor, not just the mock

At 89.52% of the depth budget there is no margin, so the mock's figure was checked against a real draw at the size actually deployed. Draw 1 on Sepolia descended 5 levels over 24 seeded stakes, against a published total weight of 124,000,000, and used 2,566,618 gas.

mock, height 5       4,476,000 HCU depth
Sepolia, height 5    4,476,000 HCU depth   9,134,672 global
difference            0.00%

Had they disagreed by even one percent the register would have had to drop to height 4. They agreed to the unit. Every figure on this page is produced by test/HCU.t.ts and test/Calibration.t.ts at commit 3f79c22, and the live record is deployments/sepolia-livedraw.json.

Scale is more shards, not a bigger tree

Depth is the binding budget, and it cannot be checkpointed across transactions the way global work can. Too much work splits into several transactions; a chain that is too long does not split at all. So this is a hard ceiling rather than a soft one, and the protocol adds shards rather than pretending the tree can grow.