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
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 EVM | Result |
|---|---|---|
| P1 | the first deposit mints one share for one asset | holds |
| P2 | rounding never favours the holder: assets(shares(x)) <= x | holds |
| P3 | a donation to the vault does not move the share price | holds |
| P4 | the inflation attack is unprofitable: the victim keeps 99.9% of a deposit made after a 10,000x donation | holds |
| P5 | redemption pays exactly what convertToAssets quoted | holds |
| P6 | totalAssets tracks deposits rather than the token balance | holds |
| P7 | a protocol fee above the cap in code reverts | holds |
| P8 | a protocol fee at the cap is accepted | holds |
| P9 | a stranger cannot set the fee | holds |
| P10 | no owner-only function reaches the principal: after calling every one, the depositor still redeems in full | holds |
| P11 | degauss stops deposits and does NOT stop redemptions | holds |
| P12 | the Field raises the share price and accrues exactly the capped fee | holds |
| P13 | after several operations the vault still holds at least totalAssets plus accrued fees, and every share outstanding still converts to no more than that | holds |
| P14 | the whole user journey works in order — deploy, mint, deposit, Field, redeem — and the depositor ends up with more than they put in | holds |
| P15 | the 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.