Built on Robinhood Chain · #58,637,577

Cupel

A vault tells you what your shares are worth. That is not the same as what the vault will let you take, and the standard says the second number is a promise. On this chain, 15 of 43 live vaults will not keep it.

ERC-4626 vault 26 properties run on chain shortfalls shared pro rata

What the standard actually says

A quote is not advice.
It is a claim about the next transaction.

EIP-4626 is unusually blunt about one function:

maxWithdraw — “MUST return the maximum amount of assets that could be transferred from owner through withdraw and not cause a revert.”

So it is falsifiable. Ask a vault what you can take, then ask it to give you exactly that, and see which answer it stands behind. No fork, no testnet, no key: eth_call runs the vault's own bytecode against its own storage at head, and costs nothing.

overstated the vault quoted a number and reverted on it4
quoted nothing maxWithdraw returned zero — nothing may leave11
honest paid the quote in full, and refused one unit more26
understated paid it, and would have paid more — safe, but not “the maximum”2

49 vaults in the census; 43 could be measured at head. 4 have no shares outstanding and 2 had no holder inside the scanned window — both are excluded and counted rather than folded in. Every row is published.

The number a holder feels

The quote is the last thing you check, and the first thing that fails.

15 of 43live vaults that will not honour the exit they quote
11quote nothing at all — maxWithdraw returns zero for their own largest holder
4quote a number, and revert when asked for exactly that number

The largest position quoted nothing

In STATE, the largest holder this sweep reached owns shares the vault itself values at 71,348,022 DEEP. maxWithdraw for that address returns zero. The vault is holding 0.0% of what it says it is worth.

0xbfb7b3ff3d498a559b946b836d26f0e168f273d5

Why it is called Cupel

A claim about metal is settled by fire,
not by the seller.

A cupel is a small porous cup of bone ash. The sample goes into it molten, and everything that is not the noble metal is drawn out into the walls of the cup. What is left sitting in the bottom is a bead of the real thing, and it is weighed. That number is the number — and it is allowed to disagree with the one on the invoice.

A vault's balance sheet is the invoice. It is an honest statement of what the vault owns, and it is not a statement about what you can have. maxWithdraw is the assay: EIP-4626 defines it as the amount withdraw will actually hand over, which means it can be put in the fire.

So that is what this does. Every vault on the chain is quoted, then asked for exactly its own quote, and the answer that counts is the one the EVM gives.

Fallen autumn leaves, photographed close, filling the frame
Autumn leaves, Retiro Park, Madrid. Photograph by Javier Perez Montes, CC BY-SA 4.0. All provenance →

How Cupel answers it

Two bounds, and the exit path reads the same function that published them.

The inversion

maxWithdraw is not convertToAssets(balanceOf(o)). It is the largest a for which previewWithdraw(a) still fits in your balance — solved exactly, through the same roundings withdraw will perform.

With an exit fee there are two nested ceilings between assets and shares. Converting straight through them lands one unit high, and one unit high is a revert.

The derivation →

The liquid bound

A vault that has deployed its assets is worth more than it can pay. liquid() is what can be paid this block, and a holder's claim on it is pro rata — not first come.

So a shortfall is shared in proportion. Whoever arrives first cannot take the liquid down to nothing and leave everybody behind them holding a quote the vault can no longer honour.

How the split works →

Executed, not asserted

26 properties and 20 sabotages, run on this chain's own EVM.

The properties run for real

All 26 run through eth_call with no to — real creation code on the real evaluator, against real chain state. 26 of 26 hold.

And each guard is broken on purpose

The contract is compiled again 20 times, each with one defect, and every defect must be caught by a specific property. 20 of 20 behave as declared.

Including the ones that survive

1 sabotage survives and is declared, with the reason. Publishing only the caught ones hides where the floor is.

Every property and every sabotage, with its result →

Ask it what you can take.

The app reads maxWithdraw and liquidFor straight from the contract, and builds the exact calldata it would send. No backend, no API key, no private key.

Open the app