Built on Robinhood Chain · block 58,933,985
Ullage
The number in the call is a request. The number that arrives is a fact. EIP-20 does not promise they are equal — and almost every contract holding your tokens has written down the first one.
Three sentences EIP-20 does not contain
What transfer actually promises,
and what everyone reads into it.
That the recipient gets the amount
The standard describes what happens to the SENDER. It never says the recipient's balance rises by the same number, and a token that takes a cut on the way through is not violating anything — it is being read wrongly by everything that integrates with it.
That a failure reverts
SHOULD throw, not MUST — and the reference ABI returns a
bool precisely so that a token can decline by returning false instead. Declared without a
return type, Solidity discards that value and never warns.
That the call returns anything
Some of the largest token contracts in circulation return nothing at
all, because they were written before the ABI settled. A strict bool decode reverts on a transfer that
in fact succeeded, and the units are then stuck in a contract that thinks the call failed.
What this chain actually does
Everything here delivers. That is the reason nobody checks.
2684 contracts emitted an ERC-20 Transfer in 15,000 blocks. The
170 most active were weighed, at one pinned block, down 4 paths each — three holders to a fresh
address, and the second holder to the first, which is a sell.
Every row is published.
A result of zero is not a reason to relax. It is the reason the assumption is everywhere: it has been true every time anybody looked, so nothing was ever built to notice when it stops.
An assumption that has never been tested is not the same as one that has been. The four tokens below are on this chain today, and three of them refuse without saying why — which is the shape the first real failure will arrive in.
And the moment a token from another chain is bridged here, or one is deployed with a tax on it, the difference between the two numbers stops being zero and every contract that wrote down the request is holding a figure that was never true.
| token | what it said when it refused |
|---|---|
| ND4 | nothing an empty revert buffer |
| PONS | nothing an empty revert buffer |
| QUOTRON | a custom error 0xc16ce029, and four bytes is all there is |
| SHRUB | nothing an empty revert buffer |
A refusal that says nothing is its own defect: the caller cannot tell a blacklist from a paused contract from a bug.
The contract
Every number it writes down
is a difference it measured itself.
Ullage is an escrow. You deposit, it credits you, you withdraw. The whole of it is that it never takes a quantity from an argument.
A deposit credits what arrived
balanceOf(this) after, minus before. Never
amount. A fee-on-transfer token that is sent 100 and delivers 97 is credited with 97, and a token that
reports success and moves nothing is refused rather than credited with zero.
A withdrawal debits what left
The contract's own balance before, minus after. A token that moves less than it was asked to leaves the difference credited where it belongs, and the payee's shortfall is returned to the caller rather than hidden or reverted on.
Which gives one invariant, and it is checked after every operation in the suite
booked[token] <= balanceOf(address(this))
The contract can never have promised more of a token than it is holding, because it never wrote down a number
it did not weigh. A token that claws units back from outside a transfer can still break it — a rebase, a
blacklist — and that is the one case a receiving account cannot prevent. So it is reported:
deficit() names the number and payouts follow the balance.
The zoo
Ten ways a transfer is true and useless,
each one a contract that was executed.
Not descriptions. Every row is a real contract in
contracts/Mocks.sol, deployed and run against Robinhood Chain's own EVM inside the property suite,
and the property named beside it is the one that has to notice.
| the failure | the contract | what it does | caught by |
|---|---|---|---|
| a fee on the way through | FeeToken | Moves 100 and delivers 97. Nothing reverts, nothing returns false, and every set of books that wrote down 100 is wrong from that moment on. | P2, P9, P21 |
| success, and nothing moved | LyingToken | Returns true, does not revert, and has code. It passes every check short of weighing the balance. |
P3 |
| no return value at all | SilentToken | The pre-ABI shape, and the reason SafeERC20 exists. A caller that decodes a bool reverts on a transfer that worked. |
P4 |
false instead of a revert |
FalseToken | Declines through a value that Solidity discards without a warning when the interface is declared without a return type. | P5 |
| no code at the address | — | An address with no code accepts every call and returns an empty buffer, which is byte-for-byte what a correct pre-ABI token returns. | P6 |
| half a word back | ShortReturnToken | Eight bytes. abi.decode reverts on it; a hand-rolled mload reads whatever is next in memory, which is usually non-zero and therefore "true". |
P7 |
| a cap that truncates | TruncateToken | Asked for 50, moves 20, out of both accounts, and says nothing about it. | P23 |
| a fee out of the sender | SurchargeToken | The payee is made whole and the sender is charged more than it authorised. The one shortfall this contract refuses rather than reports. | P10 |
| a callback mid-transfer | HookToken | Lands between the two balance readings, which is exactly where measuring a difference is weakest. | P11 |
| units removed from outside | RebaseToken | A downward rebase or a blacklist sweep. The one failure a receiving account genuinely cannot prevent — so it is named rather than smoothed. | P12, P22 |
Executed, not asserted
Every claim on this page was run on the chain.
eth_call with no to — no testnet, no fork, no funded account, no keyWeigh one yourself.
Paste any token and any holder. The page runs the sweep's own measurement in your browser, against live state, with no wallet and no transaction — and refuses to report a result if its control does not hold.