The Yoke Not built

Containment is not captivity.

A yoke is the closed iron path that carries flux back to where it started. Without one the field leaks into the room and the magnet weakens doing nothing. The flux inside a yoke is moving the entire time.

YOKE — v4-HOOK

YOKE / v4-HOOKNot built

What it holds

One Uniswap v4 position and the accounting around it. Deposits mint Moments at total assets over total supply; redemptions burn them at the same ratio. Assets are tracked rather than read from balanceOf, so a direct transfer into the vault cannot move the share price — which is property P3, and sabotage S2 breaks exactly that.

The share price is a mark

Two promises get advertised together in this category and they are not compatible: a price that only rises, and a share redeemable for what the position is worth. Make it a stored counter and it is monotone and has stopped measuring; make it a mark and it is honest and it sometimes falls. This one is a mark.

Fees, and the ceiling on them

protocol fee
a share of harvested fee income only, never of principal
hard cap
a constant in the contract; the setter reverts above it (P7)
owner reach
no owner-only function touches deposits (P10, P15)
emergency stop
Degauss halts the drive and never the exits (P11)

A description of something that does not exist. No contract is deployed, no address is live, and nothing here has been audited.

Execute it Computed

Do not take the tests on trust.

An eth_call may carry a state override: a map of address to code, applied before the call and discarded after. Put the compiled suite at a scratch address, call run(), and a public node executes the whole thing for free — deployments, reverts, transfers and all. No wallet, no gas, nothing written.

The half that matters

A suite that passes proves nothing until you have watched it bite.

15 properties, 4 deliberately broken copies whose bytecode ships with the site, and one edit that is not a defect and correctly catches nothing.

#Property, executed on an EVMResult
P1the first deposit mints one share for one asset holds
P2rounding never favours the holder: assets(shares(x)) <= x holds
P3a donation to the vault does not move the share price holds
P4the inflation attack is unprofitable: the victim keeps 99.9% of a deposit made after a 10,000x donation holds
P5redemption pays exactly what convertToAssets quoted holds
P6totalAssets tracks deposits rather than the token balance holds
P7a protocol fee above the cap in code reverts holds
P8a protocol fee at the cap is accepted holds
P9a stranger cannot set the fee holds
P10no owner-only function reaches the principal: after calling every one, the depositor still redeems in full holds
P11degauss stops deposits and does NOT stop redemptions holds
P12the Field raises the share price and accrues exactly the capped fee holds
P13after several operations the vault still holds at least totalAssets plus accrued fees, and every share outstanding still converts to no more than that holds
P14the whole user journey works in order — deploy, mint, deposit, Field, redeem — and the depositor ends up with more than they put in holds
P15the external surface is exactly the 25 functions written down, so a new one fails the build until somebody says what it does holds
Sabotaged copies — each must fail the suite
S1 redeem pays the assets out and forgets to decrement totalAssets
caught by P13, P14
caught
S2 the share price is read off the token balance instead of tracked deposits
caught by P3, P4, P13, P14
caught
S3 the fee cap check is dropped from the setter
caught by P7
caught
S4 an owner-only rescue() that can move deposits is added
caught by P15
caught
S0 the virtual share and virtual asset are removed from the conversion
caught by —
MISSED
Compiled with solc 0.8.26, optimizer 200 runs, deployed by overriding code at a scratch address inside an eth_call on base.drpc.org. Nothing is deployed and nothing has been audited.

Two of the four were missed the first time.

One because removing the ERC-4626 virtual-share offset changes nothing in this vault — it tracks its assets rather than reading its balance, so the donation the attack needs never reaches the share price. Two defences, one property, and the property could not tell them apart. The other because “no owner-only function reaches the principal” called a list written by hand, so an added rescue() walked past it: the property tested the list. That check is mechanical now — the runner reads the compiled ABI and fails unless the external surface is exactly the 25 functions written down.