The quote

Solve it, don't approximate it.

The obvious way to answer “what can I take out” is to ask what your shares are worth. That answer is wrong in a specific, reproducible way, and the error is exactly one unit — which is the difference between a quote and a revert.

The two roundings

Weir charges an exit fee, left behind for the holders who did not leave. So between the assets you ask for and the shares that are burned there are two conversions, and both round against you, as they must:

previewWithdraw(a) = ceil( ceil(a·B / (B−f)) · D / N )

  a = the assets you want in hand      f = the exit fee, in basis points
  B = 10,000                           N = totalAssets + 1
                                       D = totalSupply + 10^offset

A vault that answers maxWithdraw with convertToAssets(balanceOf(o)) has applied one conversion, in the other direction, with the other rounding. The result is above the real edge almost every time a fee is charged, and withdraw reverts on it — after burning nothing, having told you a moment earlier that it would work.

Peeling the ceilings

For integers, ceil(p/q) ≤ s is exactly p ≤ s·q. So each ceiling comes off from the outside in, and each one becomes a floor:

ceil(g·D/N) ≤ s          ⟺  g ≤ floor(s·N/D)        =: G
ceil(a·B/(B−f)) ≤ G      ⟺  a ≤ floor(G·(B−f)/B)

maxWithdraw(o) = floor( floor(bal·N/D) · (B−f) / B )

Two floors, no search, no slack. And it is tight, which the contract does not merely claim: property 8 executes previewWithdraw(M) ≤ balance and previewWithdraw(M+1) > balance on this chain's EVM, and property 10 takes the naive formula, shows it is strictly larger, and shows the vault reverting on it.

The crest

The second bound is liquidity. A vault that has deployed its assets is worth more than it can pay, and the honest quote is the smaller of the two:

crest()      = the assets this vault can pay out this block
crestFor(o)  = crest · balanceOf(o) / totalSupply     (rounded down)

maxWithdraw(o) = min( the inversion above , crestFor(o) )

The division is the point. Served first come, a shortfall is a race: the first holder to notice takes the whole crest and everybody behind them is quoted a number the vault can no longer honour. Shared pro rata, every holder is quoted their own share of what is actually there, and the sum of every quote is never more than the vault is holding — which is why the claim is rounded down.

Property 14 puts two equal holders in front of a crest that cannot pay both and requires neither to be quoted more than half of it. Property 15 then has the first one leave and the second one actually withdraw their whole quote — because checking that the second holder is still quoted a positive number is the flattering version of that test, and it passes against a vault that rounds each claim up.

And the exit path enforces what the quote published

There is no second formula for withdraw to drift away from. It calls maxWithdraw, refuses anything above it with ExceedsQuote(asked, quoted), and pays the rest. Property 9 checks the selector, not merely that something reverted — a guard is not tested by observing that something else also says no.

What is deliberately not here

  • No upgrade path and no owner who can take principal.
  • The steward may move assets between the vault and one named berth, and nowhere else — so the deployed fraction is visible and bounded.
  • The exit fee is capped in code at 100 bps and accrues to the holders who stayed, not to a recipient.
  • settle() is permissionless: income only a privileged address can recognise is income the holders wait for.