Executed, not asserted

Every claim, run on the real EVM

No testnet, no fork, no toolchain, no funded account and no private key. eth_call with no to executes creation code on Robinhood Chain’s own evaluator against real state and returns whatever the constructor returns.

31/31properties hold
17/17sabotages behave as expected
10state-changing functions, all declared
12,298bytes of deployed code, solc 0.8.26

The properties

One per eth_call, because this node caps call gas server-side and because a revert can then only have come from the property in that call.

#What it holdsResult
P1Share decimals are the asset’s plus the offset, and an empty vault starts at startPriceholds
P2deposit mints exactly what previewDeposit quotedholds
P3redeem pays exactly previewRedeemFor and burns exactly the shares asked forholds
P4transfer moves exactly the amount asked, in both fee modesholds
P5An infinite allowance is not decremented; a finite one isholds
P6A same-block round trip never profits the depositorholds
P7previewRedeem never over-quotes, for any ownerholds
P8Withdrawing exactly maxWithdraw does not revertholds
P9A donation moves neither the price nor totalAssetsholds
P10The first depositor cannot round the second to zeroholds
P11The identity: what the ratchet took is f times (crest − startPrice)holds
P12A path that ends where it started is still charged, and the charge is the peakholds
P13Crystallising more often never charges lessholds
P14A vault that only ever falls is charged nothingholds
P15Crystallise is permissionless and pays its caller nothingholds
P16The fee is dilution, so it moves no assets out of the vaultholds
P17giveUp equals the price the vault actually lost, measured by the harnessholds
P18The basis fee is path-independent: same endpoints, same feeholds
P19A holder who is flat pays nothing, however far the vault travelledholds
P20The fee cannot be shed by moving the shares to a fresh addressholds
P21The weighted-average basis is exactly the weighted averageholds
P22previewRedeem is a floor and previewRedeemFor is the answerholds
P23withdraw delivers exactly the assets asked forholds
P24A basis vault has no ratchet to crystallise, and says soholds
P25A transfer hook cannot re-enter deposit, and the exploit firesholds
P26sweep moves donations and only donationsholds
P27A rebalance changes what the vault is worth by nothing, and a stranger cannot call itholds
P28Recovering the fee’s own drag is a new record and is chargedholds
P29The fee is capped in code and there is no function that can set itholds
P30holds
P31holds

The sabotages

The contract is compiled again with one deliberate defect at a time. A suite that bites on everything is a suite nobody has found the floor of, so the ones expected to survive are kept and say why.

DefectWhat it breaksCaught by
giveup-restates-intent giveUp records the drop it MEANT to take instead of the price the vault actually lost P17 caught
fee-cap-removed the constructor stops capping the fee P29 caught
high-water-is-the-crest highWater() returns the crest rather than the price the last record left P28 caught
no-record-test the ratchet charges on every crystallisation, not only on records P11, P12, P14 caught
crest-net-not-gross the crest is recorded against the net price, so fees already taken hide later records P28 caught
basis-reset-on-transfer a receiver's basis is set to the current mark, so a sender sheds the fee by moving shares P20 caught
basis-ignored the exit fee is charged on the whole value rather than on the gain P8, P19, P22, P23 caught
preview-overquotes previewRedeem returns the gross amount on a basis vault, which over-quotes every holder P7, P22 caught
donation-priced totalAssets reads balanceOf instead of the tracked counters P9, P10 caught
no-mutex deposit drops its reentrancy guard P25 caught
sweep-takes-principal sweep moves the whole balance rather than only what was donated P26 caught
rebalance-free a rebalance does not charge the manager for what it takes P11, P14, P18, P19, P27, P31 caught
rebalance-rounds-down the manager's payment for the position is floored instead of rounded up P30 caught
stranger-rebalances the manager check is dropped from rebalance P27 caught
fee-on-transfer-credited deposit credits what was requested rather than what arrived P25 caught
no-offset the virtual share offset is removed P1, P12, P19 control
added-rescue an owner-only rescue() that can take anything is added to the surface ABI surface expected to survive

giveup-restates-intent

giveUp records the drop it MEANT to take instead of the price the vault actually lost

The one that makes identityResidual() a tautology. P11 cannot see it; only the harness measuring the price itself can.

no-offset

the virtual share offset is removed

The inflation attack is closed by tracked accounting, not by the offset. P10 must still pass without it, or the site is crediting the wrong defence.

added-rescue

an owner-only rescue() that can take anything is added to the surface

It changes no property, because no property calls it. It is seen ONLY by the mechanical read of the compiled ABI below — which is why that check exists, and why a hand-written list of owner functions would have tested the list.

Four things this suite got wrong before the contract did

Every one of these was found by the sabotage pass rather than by writing more tests, and each is why the pass exists.

Six properties were red on the correct contract

They then appeared in every sabotage’s caught-by list, which looks exactly like a suite biting hard. The runner now fails the build if any property is caught by every real sabotage.

A guard was “working” because something else also said no

The stranger who was supposed to be refused a rebalance held no tokens and no approval, so the call died on an allowance and the property passed against a vault with its manager check removed entirely. The stranger is armed now: it must be able to succeed but for the guard.

A state variable was written and never read

highWater was stored, and a sabotage that changed it survived because nothing could see it. It is a view derived from crest and giveUp now, which is exactly what it always was.

One sabotage needed a vault sized by measurement

The defect that makes giveUp restate its own intent rather than measure the loss only shows when the fee mint cannot land on the price it aims at. A ten-dollar vault rounds on none of twelve steps, a one-dollar vault on two, and a quarter-dollar vault on eleven. The property uses a quarter of a dollar, and it says so.